diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d583e5b3d..04483cbeb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ jobs: with: toolchain: nightly components: rustfmt, clippy + targets: wasm32-unknown-unknown - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable with: @@ -43,6 +44,14 @@ jobs: - name: cargo check wasm32 run: cargo check --target wasm32-unknown-unknown -p truapi-server + - name: cargo +nightly clippy wasm32 (truapi-provider browser backends) + run: cargo +nightly clippy -p truapi-provider --target wasm32-unknown-unknown --no-default-features --features "js smoldot networks" --all-targets -- -D warnings + + # The native-bindings build has no `ws` backend, which makes ChainSource a + # single-variant enum; --all-features never exercises that shape. + - name: cargo +nightly clippy (truapi-provider native bindings) + run: cargo +nightly clippy -p truapi-provider --no-default-features --features uniffi --all-targets -- -D warnings + - name: cargo +nightly fmt --check run: cargo +nightly fmt --check @@ -52,6 +61,27 @@ jobs: - name: cargo test run: cargo test --workspace --all-features + wasm-provider: + name: truapi-provider (wasm browser tests) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable + with: + toolchain: stable + targets: wasm32-unknown-unknown + + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + + - name: Install wasm-pack + run: cargo install wasm-pack --locked + + - name: wasm-pack test --headless --chrome + run: wasm-pack test --headless --chrome rust/crates/truapi-provider --no-default-features --features "js smoldot" + licenses: name: Dependency licenses runs-on: ubuntu-latest @@ -327,12 +357,23 @@ jobs: if: always() runs-on: ubuntu-latest needs: - [rust, licenses, codegen, ts-client, ts-host, playground, explorer, e2e] + [ + rust, + wasm-provider, + licenses, + codegen, + ts-client, + ts-host, + playground, + explorer, + e2e, + ] steps: - name: Check all jobs run: | results=( "${{ needs.rust.result }}" + "${{ needs.wasm-provider.result }}" "${{ needs.licenses.result }}" "${{ needs.codegen.result }}" "${{ needs.ts-client.result }}" diff --git a/.github/workflows/dev-publish.yml b/.github/workflows/dev-publish.yml new file mode 100644 index 000000000..ff69508eb --- /dev/null +++ b/.github/workflows/dev-publish.yml @@ -0,0 +1,121 @@ +name: Dev publish + +# Publish a dev-tagged snapshot of @parity/truapi-provider without moving +# `latest`. Stable releases go through release.yml. + +on: + workflow_dispatch: + inputs: + npm_tag_suffix: + description: 'Optional dev dist-tag suffix. Empty -> "dev-"; else "dev--". Charset: [A-Za-z0-9-].' + type: string + required: false + default: "" + # Push a `truapi-provider-dev` or `truapi-provider-dev-` tag to publish + # a dev snapshot; the part after `truapi-provider-dev-` becomes the dist-tag suffix. + push: + tags: + - "truapi-provider-dev*" + +permissions: + contents: read + +concurrency: + group: dev-publish-truapi-provider + cancel-in-progress: false + +jobs: + dev-publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + registry-url: "https://registry.npmjs.org" + + - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable + with: + toolchain: stable + targets: wasm32-unknown-unknown + + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + + - name: Install wasm-pack + run: cargo install wasm-pack --version 0.14.0 --locked + + # Stamp -dev-[-suffix].N (sorts below any future stable) + # into package.json; never committed. + - name: Compute dev version and dist-tag + id: devver + shell: bash + env: + NPM_TAG_SUFFIX: ${{ inputs.npm_tag_suffix }} + run: | + set -euo pipefail + suffix="${NPM_TAG_SUFFIX:-}" + # On a tag push, derive the suffix from the tag name + # (truapi-provider-dev[-]); workflow_dispatch supplies it directly. + if [[ -z "$suffix" && "${GITHUB_REF_TYPE:-}" == "tag" ]]; then + suffix="${GITHUB_REF_NAME#truapi-provider-dev}" + suffix="${suffix#-}" + fi + if [[ -n "$suffix" && ! "$suffix" =~ ^[A-Za-z0-9-]+$ ]]; then + echo "::error::dev suffix must match [A-Za-z0-9-]+ or be empty" + exit 1 + fi + pkg=js/packages/truapi-provider + base="$(jq -r .version "$pkg/package.json")" + stable="${base%%-*}" + IFS='.' read -r major minor patch <<< "$stable" + next="$major.$minor.$((patch + 1))" + date="$(date -u +%Y%m%d)" + if [[ -z "$suffix" ]]; then + tag="dev-${date}" + else + tag="dev-${date}-${suffix}" + fi + prefix="${next}-${tag}" + # `|| echo '[]'`: npm view 404s before the first publish; `|| true`: + # no version matches the prefix on the first same-day build. + max_n="$( + { npm view --json @parity/truapi-provider versions 2>/dev/null || echo '[]'; } \ + | jq -r '.[]?' \ + | { grep -E "^${prefix}\.[0-9]+$" || true; } \ + | sed -E "s|^${prefix}\.||" \ + | sort -n | tail -1 + )" + n=$(( ${max_n:--1} + 1 )) + version="${prefix}.${n}" + jq --arg v "$version" '.version = $v' "$pkg/package.json" > "$pkg/package.json.tmp" + mv "$pkg/package.json.tmp" "$pkg/package.json" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "Computed @parity/truapi-provider@${version} -> dist-tag ${tag}" + + - name: Build wasm + run: npm run build:wasm --prefix js/packages/truapi-provider + + - name: Pack + run: | + set -euo pipefail + mkdir -p artifacts + (cd js/packages/truapi-provider && npm pack --pack-destination "$GITHUB_WORKSPACE/artifacts/") + + - name: Upload package artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: truapi-provider-dev-${{ github.sha }} + path: artifacts/*.tgz + + - name: Publish via npm_publish_automation + uses: octokit/request-action@b91aabaa861c777dcdb14e2387e30eddf04619ae # v3.0.0 + with: + route: POST /repos/paritytech/npm_publish_automation/actions/workflows/publish.yml/dispatches + ref: main + inputs: '${{ format(''{{ "repo": "{0}", "run_id": "{1}", "artifact_name": "truapi-provider-dev-{2}", "npm_tag": "{3}" }}'', github.repository, github.run_id, github.sha, steps.devver.outputs.tag) }}' + env: + GITHUB_TOKEN: ${{ secrets.NPM_PUBLISH_AUTOMATION_TOKEN }} diff --git a/.github/workflows/update-chain-specs.yml b/.github/workflows/update-chain-specs.yml new file mode 100644 index 000000000..59e488615 --- /dev/null +++ b/.github/workflows/update-chain-specs.yml @@ -0,0 +1,69 @@ +name: Refresh Chain Specs + +# Refresh the smoldot chain specs bundled in truapi-provider (fresh relay +# lightSyncState checkpoints and genesis stateRootHash). The refresh only +# reaches consumers after a new @parity/truapi-provider is published, so this +# runs weekly rather than hourly. +on: + schedule: + # Mondays at 00:00 UTC. + - cron: "0 0 * * 1" + workflow_dispatch: + inputs: + skip_bootnode_check: + description: "Skip bootnode health check (keep existing bootnodes)" + required: false + type: boolean + default: true + +permissions: + contents: write + pull-requests: write + +jobs: + update: + name: Update Chain Specs + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0 + with: + node-version: "22" + + - name: Refresh chain specs + run: bash scripts/update-chain-specs.sh + env: + SKIP_BOOTNODE_CHECK: ${{ inputs.skip_bootnode_check == '' && 'true' || inputs.skip_bootnode_check }} + + - name: Check for changes + id: diff + run: | + if git diff --quiet rust/crates/truapi-provider/networks/; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create Pull Request + if: steps.diff.outputs.changed == 'true' + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + title: "chore(truapi-provider): refresh bundled chain specs" + body: | + Automated refresh of the smoldot chain specs bundled in truapi-provider. + + If `every_catalog_genesis_hash_matches_its_bundled_spec` fails, a chain has been + re-genesised and its `genesis_hex` in `rust/crates/truapi-provider/src/networks.rs` + needs updating too. The refresh only rewrites the specs, never the catalog constants. + The failure names the network and service, and prints the hash the refreshed spec + derives to on the left and the stale catalog constant on the right. Copy the left one + into `networks.rs`, and update any consumer that pins the old hash. + + Publish a new `@parity/truapi-provider` for consumers to pick up the refresh. + branch: auto/chain-spec-update + commit-message: "chore(truapi-provider): refresh bundled chain specs" + delete-branch: true + labels: dependencies diff --git a/.gitignore b/.gitignore index 73120fc2e..34260c5fb 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,9 @@ playground/test/generated/ # Auto-generated FFI / WASM binding outputs android/truapi-host/src/main/kotlin/generated/ android/truapi-host/src/main/jniLibs/ +android/truapi-provider/src/main/kotlin/generated/ +android/truapi-provider/src/main/jniLibs/ +ios/truapi-provider/Binaries/ rust/crates/truapi-server/pkg/ js/packages/truapi/src/generated/ js/packages/truapi/dist/generated/ diff --git a/CLAUDE.md b/CLAUDE.md index 5bd348142..8fd0f1da0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,6 +12,7 @@ rust/crates/ truapi-codegen/ rustdoc JSON → TypeScript client + Rust dispatcher truapi-macros/ #[wire(id = N)] proc-macro truapi-platform/ Host syscall traits (storage, navigation, consent, ...) + truapi-provider/ network provider backends (WebSocket RPC or smoldot light-client) truapi-server/ Rust runtime hosts implement; ships as WASM (browser/node) js/packages/ truapi/ @parity/truapi TS package; generated TS lives under ignored paths @@ -21,6 +22,11 @@ js/packages/ WASM bundle (gitignored) under dist/wasm/web/, built via `make wasm` js/container/ TS lockdown container for the iOS host web view; `npm run build` bundles it into ios/truapi-host/Sources/TrUAPIHost/Resources/ +ios/truapi-provider/ TrUAPIProvider Swift package (chain transport over UniFFI); + second product of the root Package.swift, released on its + own tag (@parity/ios-provider@) via its scripts/ +android/truapi-provider/ truapi-provider-android AAR; unlike truapi-host it bundles + the cdylib, so consumers need no Rust toolchain ios/truapi-host/ TrUAPIHost Swift package over the truapi-server UniFFI core; SPM manifest at the repo root (Package.swift), rebuild via ios/truapi-host/scripts/rebuild.sh diff --git a/Cargo.lock b/Cargo.lock index 5506d05e0..ec02b3e09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1870,6 +1870,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -2018,6 +2037,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.11.0" @@ -2028,9 +2053,11 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2385,9 +2412,11 @@ checksum = "72c4b1f204b655b36b24dc4939af20366c649431d4711863bbbae5c495f3eeb4" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", + "jsonrpsee-server", "jsonrpsee-types", "jsonrpsee-wasm-client", "jsonrpsee-ws-client", + "tokio", ] [[package]] @@ -2422,10 +2451,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49bfa9334963e1c85866b39dff3ffcc81f1c286eb23334267c5cb97677543a4" dependencies = [ "async-trait", + "bytes", "futures-timer", "futures-util", + "http", + "http-body", + "http-body-util", "jsonrpsee-types", + "parking_lot", "pin-project", + "rand 0.8.7", "rustc-hash", "serde", "serde_json", @@ -2436,6 +2471,33 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "jsonrpsee-server" +version = "0.24.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c625c78b8d545478370b6e7a2a191b0d921f831a9eef38dc1e7efb57e7a5472f" +dependencies = [ + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.4.13", + "tracing", +] + [[package]] name = "jsonrpsee-types" version = "0.24.11" @@ -3484,7 +3546,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tower", + "tower 0.5.3", "tower-http", "tower-service", "url", @@ -3508,6 +3570,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + [[package]] name = "rustc-hash" version = "2.1.3" @@ -4259,6 +4327,7 @@ dependencies = [ "base64", "bytes", "futures", + "http", "httparse", "log", "rand 0.8.7", @@ -4785,6 +4854,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -4840,6 +4910,21 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.3" @@ -4867,7 +4952,7 @@ dependencies = [ "http", "http-body", "pin-project-lite", - "tower", + "tower 0.5.3", "tower-layer", "tower-service", "url", @@ -4891,6 +4976,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5032,6 +5118,42 @@ dependencies = [ "zeroize", ] +[[package]] +name = "truapi-provider" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "derive_more 2.1.1", + "futures", + "futures-timer", + "getrandom 0.2.17", + "hex", + "js-sys", + "jsonrpsee", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "pin-project", + "rand 0.8.7", + "send_wrapper", + "serde_json", + "smoldot", + "smoldot-light", + "thiserror 1.0.69", + "tokio", + "tokio-tungstenite 0.24.0", + "tracing", + "tracing-subscriber", + "truapi", + "truapi-platform", + "uniffi", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", + "web-time", +] + [[package]] name = "truapi-server" version = "0.1.0" diff --git a/Makefile b/Makefile index e3f06063f..c0b520538 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ JS_PACKAGES := js/packages EXPLORER := explorer DOTLI := hosts/dotli HOST_WASM_PKG := $(JS_PACKAGES)/truapi-host +PROVIDER_WASM_PKG := $(JS_PACKAGES)/truapi-provider HOST_CALLBACKS_GENERATED := $(HOST_WASM_PKG)/src/generated/host-callbacks.ts HOST_WASM_ADAPTER_GENERATED := $(HOST_WASM_PKG)/src/generated/host-callbacks-adapter.ts HOST_WASM_WORKER_CALLBACKS_GENERATED := $(HOST_WASM_PKG)/src/generated/worker-callbacks.ts @@ -67,8 +68,9 @@ codegen: ## Regenerate generated TS/Rust artifacts from the Rust crates. ./scripts/codegen.sh cd $(PLAYGROUND) && rm -rf node_modules/@parity && yarn install -wasm: ## Rebuild the truapi-server WASM artifacts under js/packages/truapi-host/dist/wasm/. +wasm: ## Rebuild the truapi-server and truapi-provider WASM bundles under js/packages/*/dist/. cd $(HOST_WASM_PKG) && npm run build:wasm + cd $(PROVIDER_WASM_PKG) && npm run build:wasm wasm-crypto-test: ## Run crypto/vector tests on wasm32 via wasm-pack/node. wasm-pack test --node rust/crates/truapi-server --test wasm_crypto_vectors --no-default-features @@ -82,8 +84,10 @@ UNIFFI_CDYLIB_DIR := target/codegen UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) UNIFFI_CDYLIB := $(UNIFFI_CDYLIB_DIR)/libtruapi_server.dylib +PROVIDER_CDYLIB := $(UNIFFI_CDYLIB_DIR)/libtruapi_provider.dylib else UNIFFI_CDYLIB := $(UNIFFI_CDYLIB_DIR)/libtruapi_server.so +PROVIDER_CDYLIB := $(UNIFFI_CDYLIB_DIR)/libtruapi_provider.so endif UNIFFI_SWIFT_TMP := target/uniffi-swift-out @@ -202,6 +206,34 @@ android-jni: ## Cross-compile libtruapi_server.so for Android ABIs into jniLibs android-publish-local: uniffi-kotlin ## Generate Kotlin bindings, then publish the AAR to ~/.m2 (needs Gradle + JDK 17). The AAR does not bundle the cdylib; consumers build it per ABI (see android-jni). gradle :truapi-host:publishReleasePublicationToMavenLocal +# truapi-provider ships as its own per-platform artifacts (iOS xcframework, +# Android AAR, npm wasm) so a host consumes chain transport without depending on +# the Rust crate. The `uniffi` feature carries no `ws` backend: these builds are +# the light client alone. +PROVIDER_KOTLIN_OUT := android/truapi-provider/src/main/kotlin/generated +PROVIDER_JNILIBS := android/truapi-provider/src/main/jniLibs + +provider-ios: ## Build the TrUAPIProvider Swift bindings + xcframework (adds --sim-only via SIM_ONLY=1). + bash ios/truapi-provider/scripts/rebuild.sh $(if $(SIM_ONLY),--sim-only,) + +provider-kotlin: ## Regenerate Kotlin UniFFI bindings from the truapi-provider cdylib. + $(CARGO) build -p truapi-provider --profile codegen --no-default-features --features uniffi + rm -rf $(PROVIDER_KOTLIN_OUT) + mkdir -p $(PROVIDER_KOTLIN_OUT) + $(CARGO) run -p uniffi-bindgen-cli -- generate \ + --library $(PROVIDER_CDYLIB) \ + --language kotlin \ + --out-dir $(PROVIDER_KOTLIN_OUT) + +provider-android-jni: ## Cross-compile libtruapi_provider.so for Android ABIs into the module's jniLibs (needs cargo-ndk + NDK). + @command -v cargo-ndk >/dev/null || { echo "cargo-ndk not found: cargo install cargo-ndk"; exit 1; } + $(CARGO) ndk $(foreach abi,$(ANDROID_ABIS),-t $(abi)) \ + -o $(PROVIDER_JNILIBS) \ + build --release -p truapi-provider --no-default-features --features uniffi + +provider-android-publish-local: provider-kotlin provider-android-jni ## Publish the self-contained provider AAR (bindings + cdylib) to ~/.m2. + gradle :truapi-provider:publishReleasePublicationToMavenLocal + test: ## Run Rust + TypeScript client tests. cargo test --workspace cd $(TRUAPI_PKG) && npm test diff --git a/Package.swift b/Package.swift index d60724320..71365505e 100644 --- a/Package.swift +++ b/Package.swift @@ -1,11 +1,13 @@ // swift-tools-version: 5.10 // -// TrUAPIHost — iOS host package for the Rust TrUAPI core, consumed as an SPM -// git dependency (the manifest must live at the repo root for that). All -// package sources live under ios/truapi-host/. The uniffi-generated bindings -// and the container bundle are committed build outputs (regenerate with -// ios/truapi-host/scripts/rebuild.sh); the xcframework is gitignored and -// distributed as a GitHub release asset (ios/truapi-host/scripts/publish.sh). +// TrUAPIHost — iOS host package for the Rust TrUAPI core — and TrUAPIProvider — +// chain transport over an embedded smoldot light client with a bundled chain-spec +// catalog — consumed as SPM git dependencies (the manifest must live at the repo +// root for that). Package sources live under ios/truapi-host/ and +// ios/truapi-provider/. The two products are independent and release on separate +// tags. For both, the uniffi-generated bindings are committed build outputs +// (regenerate with the package's scripts/rebuild.sh) while the xcframework is +// gitignored and distributed as a GitHub release asset (scripts/publish.sh). import Foundation import PackageDescription @@ -29,11 +31,36 @@ let binaryTarget: Target = useLocalBinary checksum: publishedBinaryChecksum ) +// Set TRUAPI_PROVIDER_USE_LOCAL_BINARY=1 to build against the locally generated +// ios/truapi-provider/Binaries/truapi_provider.xcframework (run rebuild.sh +// first). Separate from TRUAPI_USE_LOCAL_BINARY because the products release +// independently: a local build of one must not require a local build of the other. +let useLocalProviderBinary = + ProcessInfo.processInfo.environment["TRUAPI_PROVIDER_USE_LOCAL_BINARY"] == "1" + +// Set by ios/truapi-provider/scripts/publish.sh. No release exists yet, so remote +// resolution fails on the checksum until the first publish. + +let providerBinaryURL = "https://github.com/paritytech/truapi/releases/download/%40parity%2Fios-provider%400.0.0-unpublished/truapi_provider.xcframework.zip" +let providerBinaryChecksum = "0000000000000000000000000000000000000000000000000000000000000000" + +let providerBinaryTarget: Target = useLocalProviderBinary + ? .binaryTarget( + name: "truapi_providerFFI_binary", + path: "ios/truapi-provider/Binaries/truapi_provider.xcframework" + ) + : .binaryTarget( + name: "truapi_providerFFI_binary", + url: providerBinaryURL, + checksum: providerBinaryChecksum + ) + let package = Package( name: "TrUAPIHost", platforms: [.iOS(.v17)], products: [ - .library(name: "TrUAPIHost", targets: ["TrUAPIHost"]) + .library(name: "TrUAPIHost", targets: ["TrUAPIHost"]), + .library(name: "TrUAPIProvider", targets: ["TrUAPIProvider"]), ], targets: [ .systemLibrary( @@ -68,5 +95,17 @@ let package = Package( dependencies: ["TrUAPIHost"], path: "ios/truapi-host/Tests" ), + .systemLibrary( + name: "truapi_providerFFI", + path: "ios/truapi-provider/Sources/truapi_providerFFI/include", + pkgConfig: nil, + providers: [] + ), + providerBinaryTarget, + .target( + name: "TrUAPIProvider", + dependencies: ["truapi_providerFFI", "truapi_providerFFI_binary"], + path: "ios/truapi-provider/Sources/TrUAPIProvider" + ), ] ) diff --git a/README.md b/README.md index e5a4f4d2c..d29e3024e 100644 --- a/README.md +++ b/README.md @@ -58,16 +58,21 @@ rust/crates/ truapi-codegen/ rustdoc JSON to TypeScript client + Rust dispatcher truapi-macros/ #[wire(id = N)] proc-macro truapi-platform/ Host syscall traits used by truapi-server (storage, navigation, consent, ...) + truapi-provider/ Network provider backends (WebSocket RPC or smoldot light-client) truapi-server/ Host runtime: dispatcher, typed SCALE logic, chain signing, WASM surface js/packages/ truapi/ @parity/truapi TypeScript client truapi-host/ @parity/truapi-host: WASM-backed host runtime; entries `.` (shared host types), `/web` (iframe + Web Worker), `/worker-runtime` + truapi-provider/ @parity/truapi-provider: WASM ChainProvider backends + (embedded smoldot light client + remote WebSocket RPC) js/container/ TS lockdown container for the iOS host web view; bundles into ios/truapi-host/Sources/TrUAPIHost/Resources/truapi-container.js android/truapi-host/ Kotlin host adapter package over the truapi-server UniFFI core +android/truapi-provider/ truapi-provider-android: chain transport AAR (bindings + cdylib) ios/truapi-host/ Swift host adapter package over the truapi-server UniFFI core +ios/truapi-provider/ TrUAPIProvider Swift package: chain transport over UniFFI playground/ Interactive Next.js playground (truapi-playground.dot) hosts/dotli/ dotli host, vendored as a submodule docs/ Design docs, RFCs, feature proposals @@ -95,6 +100,24 @@ a single package with tree-shakeable subpath entries: - `@parity/truapi-host/worker-runtime` is the Web Worker entrypoint so the WASM core can run off the page main thread. +### Chain transport + +A host that serves chain traffic itself embeds the `truapi-provider` crate: an +embedded smoldot light client plus a bundled chain-spec catalog, addressed by +genesis hash, so the host ships no chain specs and never refreshes them. The crate +compiles to one binary artifact per platform, each exposing the same +`ChainProvider` contract, so a consumer needs neither a Rust toolchain nor a +dependency on the crate: + +- [`@parity/truapi-provider`](js/packages/truapi-provider) is the WASM build for + browser and webview hosts, rebuilt by `make wasm` alongside the host bundle. +- [`TrUAPIProvider`](ios/truapi-provider) is the second product of the root + `Package.swift`, an xcframework plus committed Swift bindings, built by + `make provider-ios`. +- [`truapi-provider-android`](android/truapi-provider) is an AAR carrying the + Kotlin bindings and the cdylib per ABI, built by + `make provider-android-publish-local`. + ## How it works 1. The protocol is defined as Rust traits in [`rust/crates/truapi/`](rust/crates/truapi/), with each method tagged `#[wire(id = N)]` for a stable byte-level dispatch table. Every method's doc comment must carry a ` ```ts ` example, which codegen extracts into the playground's EXAMPLE tab; the build fails if any method is missing one. diff --git a/android/truapi-provider/README.md b/android/truapi-provider/README.md new file mode 100644 index 000000000..6dc4418d0 --- /dev/null +++ b/android/truapi-provider/README.md @@ -0,0 +1,122 @@ +# TrUAPI Android chain transport + +*Kotlin bindings for the `truapi-provider` crate (UniFFI). An embedded smoldot light client and the bundled chain-spec catalog stay in Rust; the host addresses a chain by genesis hash and exchanges JSON-RPC strings.* + +> **Status:** there is no remote coordinate yet. JitPack cannot serve this module as it stands — it builds from a git tag, and both the bindings and the cdylib are generated rather than committed — and no hosted Maven publication is wired up. Until one is, integrate with `make provider-android-publish-local` + `mavenLocal()`. The module itself has not been built on CI or a machine with the Android toolchain, so treat the Gradle wiring below as unverified. + +Unlike [`truapi-host`](../truapi-host), whose AAR leaves the cdylib to the integrator, this AAR bundles `libtruapi_provider.so` for every published ABI. That is the point of the package: a consumer adds one coordinate and calls `ChainProvider()`, with no Rust toolchain and no dependency on the crate. + +## Consume + +```kotlin +// settings.gradle.kts +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + mavenLocal() // until a hosted publication exists, see Status above + } +} +``` + +```kotlin +// app/build.gradle.kts +dependencies { + implementation("io.parity:truapi-provider-android:0.1.0") +} +``` + +The consuming app must declare `android.permission.INTERNET` — the light client dials peers over TCP and WebSocket. + +Chain specs are compiled into the cdylib, so the app ships no spec files of its own and never refreshes them. Picking up a spec refresh means taking a newer version of this artifact. + +### Compatibility + +- **minSdk**: 29 (Android 10). Matches the `truapi-host` floor so a host can depend on both. +- **ABIs**: `arm64-v8a`, `armeabi-v7a`, `x86_64` (`ANDROID_ABIS` overrides the set). Each carries a copy of the light client, so the AAR is large; split APKs or an App Bundle keep the shipped size to one ABI. +- **Transitive dependency**: the AAR pulls `net.java.dev.jna:jna:5.14.0` (UniFFI's runtime), shared with `truapi-host` when both are present. + +## Public surface + +Everything is generated from [`ffi.rs`](../../rust/crates/truapi-provider/src/ffi.rs) into `uniffi.truapi_provider.*`: + +- `ChainProvider` - construct **one per process** and share it. Every connection runs on the single embedded light client, so they share sync, peers, and warm state while keeping their own request queue and response stream. `connect(genesisHash, listener)` resolves the network from the bundled catalog (relay wiring and statement-store placement included), so the 32-byte genesis hash is the only argument. +- `ChainMessageListener` - the host implements it; `onMessage(message)` receives each JSON-RPC response and notification, `onClosed()` fires once the stream ends. +- `ChainConnection` - `send(request)` queues a request, `close()` tears the connection down. +- `ChainProviderError` - `Connect` when the genesis is outside the catalog or the transport fails, `BadGenesis` when the hash is not 32 bytes. + +## Architecture + +```text +host app + ChainProvider().connect(genesisHash, listener) + | + v +libtruapi_provider.so (embedded smoldot + bundled chain-spec catalog) + → one light client per process, one added chain per connection + → responses pumped on a Rust-owned thread into ChainMessageListener +``` + +A connection is a raw JSON-RPC string pipe. The provider does no decoding: what smoldot answers is what the listener receives. + +## Example + +> **Threading:** the crate pumps each connection's responses on a background thread it owns, so `onMessage` and `onClosed` are never called on the UI thread — marshal any UI work onto it with `Handler(Looper.getMainLooper())` or a `Dispatchers.Main` `CoroutineScope`. `connect(genesisHash, listener)` is blocking and adds the chain on the calling thread, so keep it off the UI thread. + +```kt +import android.os.Handler +import android.os.Looper +import uniffi.truapi_provider.ChainConnection +import uniffi.truapi_provider.ChainMessageListener +import uniffi.truapi_provider.ChainProvider + +class Responses : ChainMessageListener { + private val main = Handler(Looper.getMainLooper()) + + // A JSON-RPC response or subscription notification, verbatim from smoldot. + override fun onMessage(message: String) { + main.post { /* decode and render */ } + } + + override fun onClosed() { + main.post { /* the stream ended: drop the connection */ } + } +} + +// One provider per process; hold it for the app's lifetime. +val provider = ChainProvider() + +// 32 raw bytes, not a hex string. Must be a chain in the bundled catalog. +val genesis = ByteArray(32) +val connection: ChainConnection = provider.connect(genesis, Responses()) + +connection.send("""{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_genesisHash","params":[]}""") + +// On teardown: +connection.close() +``` + +## Maintainers: publishing locally + +```bash +make provider-android-publish-local +``` + +That regenerates the Kotlin bindings, cross-compiles the cdylib for every ABI, and publishes to `~/.m2/repository/io/parity/truapi-provider-android//`. It needs Gradle, JDK 17, the Android NDK, and `cargo-ndk` (`cargo install cargo-ndk`). + +Publishing refuses to run when `src/main/jniLibs` holds no `libtruapi_provider.so`: such an AAR resolves fine and then fails at the first `ChainProvider()` with `UnsatisfiedLinkError`, which is a much worse failure than a build error. The two steps behind it are available separately: + +```bash +make provider-kotlin # regenerate the bindings only +make provider-android-jni # cross-compile the cdylib only +``` + +Both the generated bindings under `src/main/kotlin/generated/uniffi/` and the `.so` files under `src/main/jniLibs/` are gitignored build outputs. + +## Regenerating the UniFFI bindings + +```bash +make provider-kotlin +``` + +That builds the crate with the `codegen` profile and runs the workspace `uniffi-bindgen-cli`. The `codegen` profile is required because uniffi-bindgen scans the cdylib's exported metadata symbols, which the `release` profile strips — a plain `--release` build produces a stripped library and no bindings. (`make uniffi-kotlin` does the same for the host package.) diff --git a/android/truapi-provider/build.gradle.kts b/android/truapi-provider/build.gradle.kts new file mode 100644 index 000000000..cea115d57 --- /dev/null +++ b/android/truapi-provider/build.gradle.kts @@ -0,0 +1,149 @@ +// TrUAPI Android chain transport. +// +// Publishes `io.parity:truapi-provider-android` to Maven: the UniFFI Kotlin +// bindings for the `truapi-provider` crate (embedded smoldot light client plus +// the bundled chain-spec catalog), together with `libtruapi_provider.so` per +// Android ABI. Hosts address a chain by genesis hash and exchange raw JSON-RPC +// strings; the crate owns the light client and the specs. +// +// Bundling the cdylib is what distinguishes this from `truapi-host`, whose AAR +// leaves it to the integrator. It makes the jniLibs a publish-time requirement +// rather than an optional extra, enforced by `verifyJniLibs` below. + +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") + id("maven-publish") +} + +android { + namespace = "io.parity.truapi.provider" + compileSdk = 34 + + lint { + // Suppresses the NewApi false positive on the UniFFI-generated cleaner + // (runtime-guarded via Class.forName). See lint.xml. + lintConfig = file("lint.xml") + } + + defaultConfig { + // Matches the truapi-host floor so a host can depend on both. + minSdk = 29 + consumerProguardFiles("consumer-rules.pro") + } + + sourceSets { + getByName("main") { + java.srcDirs("src/main/kotlin") + manifest.srcFile("src/main/AndroidManifest.xml") + jniLibs.srcDirs("src/main/jniLibs") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + publishing { + singleVariant("release") { + withSourcesJar() + withJavadocJar() + } + } +} + +dependencies { + // UniFFI Kotlin bindings use JNA for FFI. + api("net.java.dev.jna:jna:5.14.0@aar") + // UniFFI async functions and callbacks use cancellable continuations and jobs. + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") +} + +// Coordinates for the local Maven publication (`publishToMavenLocal`), which is +// the only publication that exists today. JitPack cannot serve this module: it +// builds from a git tag, and both the bindings and the cdylib are generated +// rather than committed. A remote coordinate needs a hosted Maven repository. +val publicationGroup = "io.parity" +val publicationArtifact = "truapi-provider-android" +val publicationVersion = "0.1.0" + +group = publicationGroup +version = publicationVersion + +// A publication without the cdylib would resolve and then fail at the first +// `ChainProvider()` with UnsatisfiedLinkError, so refuse to publish one. +val verifyJniLibs = + tasks.register("verifyJniLibs") { + description = "Fails when src/main/jniLibs has no .so, which would publish an unusable AAR." + doLast { + val libs = file("src/main/jniLibs") + val found = libs.walkTopDown().filter { it.name == "libtruapi_provider.so" }.toList() + if (found.isEmpty()) { + throw GradleException( + "no libtruapi_provider.so under ${libs.path} — run `make android-jni-provider` first" + ) + } + logger.lifecycle("bundling ${found.size} ABI(s): ${found.map { it.parentFile.name }}") + } + } + +tasks.matching { it.name.startsWith("publish") }.configureEach { dependsOn(verifyJniLibs) } + +publishing { + publications { + register("release") { + groupId = publicationGroup + artifactId = publicationArtifact + version = publicationVersion + + afterEvaluate { + from(components["release"]) + } + + pom { + name.set("TrUAPI Android chain transport") + description.set( + "Kotlin bindings for the TrUAPI provider crate (UniFFI): an " + + "embedded smoldot light client with a bundled chain-spec " + + "catalog, addressed by genesis hash. Bundles " + + "libtruapi_provider.so, so no Rust toolchain is required." + ) + url.set("https://github.com/paritytech/truapi") + licenses { + license { + name.set("MIT") + url.set("https://github.com/paritytech/truapi/blob/main/LICENSE") + } + license { + name.set("Apache-2.0") + url.set("https://github.com/paritytech/truapi/blob/main/LICENSE-APACHE") + } + } + scm { + connection.set("scm:git:https://github.com/paritytech/truapi.git") + developerConnection.set("scm:git:ssh://git@github.com/paritytech/truapi.git") + url.set("https://github.com/paritytech/truapi") + } + developers { + developer { + name.set("Parity Technologies") + email.set("admin@parity.io") + organization.set("Parity Technologies") + organizationUrl.set("https://parity.io") + } + } + } + } + } + + repositories { + // `gradle publishToMavenLocal` during development; consumers resolve it + // through `mavenLocal()` until a hosted repository is wired up. + mavenLocal() + } +} diff --git a/android/truapi-provider/consumer-rules.pro b/android/truapi-provider/consumer-rules.pro new file mode 100644 index 000000000..62e7c3e29 --- /dev/null +++ b/android/truapi-provider/consumer-rules.pro @@ -0,0 +1,10 @@ +# ProGuard / R8 rules applied to consumers of `io.parity:truapi-provider-android`. +# +# JNA reflects into our generated UniFFI types at runtime, so the bindings +# package must survive shrinking. + +-keep class uniffi.truapi_provider.** { *; } + +# JNA itself. +-keep class com.sun.jna.** { *; } +-keepclassmembers class * extends com.sun.jna.** { *; } diff --git a/android/truapi-provider/lint.xml b/android/truapi-provider/lint.xml new file mode 100644 index 000000000..5dfd6be45 --- /dev/null +++ b/android/truapi-provider/lint.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/android/truapi-provider/src/main/AndroidManifest.xml b/android/truapi-provider/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8072ee00d --- /dev/null +++ b/android/truapi-provider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/deny.toml b/deny.toml index 43a7c2651..1a454ec77 100644 --- a/deny.toml +++ b/deny.toml @@ -1,5 +1,13 @@ # cargo-deny configuration. Run `cargo deny check licenses`. -# Outbound licence is MIT; only permissive inbound licences are allowed. +# Outbound licence is MIT; only permissive inbound licences are allowed, +# with a scoped exception for the smoldot light-client crates: their +# Classpath exception explicitly permits linking from differently-licensed +# code. + +[graph] +# Resolve the worst-case graph so optional backends (e.g. truapi-provider's +# smoldot feature) are covered by the license gate. +all-features = true [licenses] allow = [ @@ -17,10 +25,14 @@ allow = [ ] confidence-threshold = 0.8 -# uniffi is MPL-2.0: file-level weak copyleft, consumed unmodified as a -# dependency, which MPL-2.0 permits without affecting the MIT outbound licence. -# Scoped per crate so MPL-2.0 stays disallowed everywhere else. +# Scoped per-crate licence exceptions, disallowed everywhere else: +# - smoldot / smoldot-light are GPL-3.0-or-later WITH Classpath-exception-2.0, +# whose Classpath exception permits linking from the MIT outbound licence. +# - uniffi is MPL-2.0: file-level weak copyleft, consumed unmodified as a +# dependency, which MPL-2.0 permits without affecting the MIT outbound licence. exceptions = [ + { name = "smoldot", allow = ["GPL-3.0-or-later WITH Classpath-exception-2.0"] }, + { name = "smoldot-light", allow = ["GPL-3.0-or-later WITH Classpath-exception-2.0"] }, # Ring-VRF implementation shared with the Individuality runtimes and Nova. # The Classpath exception permits linking it without changing this crate's # MIT outbound licence. @@ -31,7 +43,9 @@ exceptions = [ { name = "error-code", allow = ["BSL-1.0"] }, { name = "uniffi", allow = ["MPL-2.0"] }, { name = "uniffi_bindgen", allow = ["MPL-2.0"] }, + { name = "uniffi_checksum_derive", allow = ["MPL-2.0"] }, { name = "uniffi_core", allow = ["MPL-2.0"] }, + { name = "uniffi_testing", allow = ["MPL-2.0"] }, { name = "uniffi_internal_macros", allow = ["MPL-2.0"] }, { name = "uniffi_macros", allow = ["MPL-2.0"] }, { name = "uniffi_meta", allow = ["MPL-2.0"] }, diff --git a/ios/truapi-provider/.gitignore b/ios/truapi-provider/.gitignore new file mode 100644 index 000000000..077e55329 --- /dev/null +++ b/ios/truapi-provider/.gitignore @@ -0,0 +1,6 @@ +.build/ +.swiftpm/ +DerivedData/ +*.xcodeproj/xcuserdata/ +Package.resolved +Binaries/ diff --git a/ios/truapi-provider/README.md b/ios/truapi-provider/README.md new file mode 100644 index 000000000..a27b2f3b6 --- /dev/null +++ b/ios/truapi-provider/README.md @@ -0,0 +1,117 @@ +# TrUAPI iOS chain transport + +*Swift shell over the `truapi-provider` crate (UniFFI). An embedded smoldot light client and the bundled chain-spec catalog stay in Rust; the host addresses a chain by genesis hash and exchanges JSON-RPC strings.* + +> **Status:** no `@parity/ios-provider` release exists yet, so `providerBinaryURL` and `providerBinaryChecksum` in the root `Package.swift` are placeholders and remote resolution of `TrUAPIProvider` fails on the checksum. Until the first `scripts/publish.sh` run, build against the local xcframework (`make provider-ios`, then `TRUAPI_PROVIDER_USE_LOCAL_BINARY=1`) or depend on the package by path. Everything below describes the target design. + +The package lives in the truapi repo next to the Rust crate it wraps. `Package.swift` sits at the **repo root** (SPM requires that for git-URL dependencies) and declares two products: [`TrUAPIHost`](../truapi-host) and `TrUAPIProvider`. They are independent — a host depends on whichever it needs — and release on separate tags, so each has its own local-binary toggle. + +## What this package is for + +The `TrUAPIProvider` SPM product an iOS host imports when it wants to serve chain traffic itself rather than proxying it. It carries: + +- `Sources/TrUAPIProvider/truapi_provider.swift` and `Sources/truapi_providerFFI/include/` — the generated UniFFI bindings. There is no hand-written Swift shell: the crate's [`ffi.rs`](../../rust/crates/truapi-provider/src/ffi.rs) is the whole surface. +- the crate as a binary target — a GitHub release asset by default (`providerBinaryURL` in the root `Package.swift`), or the locally built `Binaries/truapi_provider.xcframework` when `TRUAPI_PROVIDER_USE_LOCAL_BINARY=1`. + +The bindings are committed build outputs; the xcframework is **gitignored** and distributed as a GitHub release asset. Two scripts split the lifecycle: + +```bash +./scripts/rebuild.sh # build the crate for device + simulator, regenerate + # the bindings, and bundle Binaries/truapi_provider.xcframework +./scripts/publish.sh # zip the built xcframework, upload it to the + # "@parity/ios-provider " GitHub release, + # and point the root Package.swift at it + # (URL + checksum) +``` + +Run `rebuild.sh` after changing anything in the crate's `uniffi` surface — the `ChainProvider` methods, `ChainMessageListener`, `ChainProviderError` — or after a chain-spec refresh, and commit the regenerated bindings together with the source change. Pass `--sim-only` (or `make provider-ios SIM_ONLY=1`) to skip the device slice while iterating; `publish.sh` refuses a simulator-only xcframework. + +## Integrating in an iOS app + +Add the package as an SPM dependency and link the `TrUAPIProvider` product into the app target: + +```swift +.package(url: "https://github.com/paritytech/truapi.git", branch: "main") +``` + +```swift +.product(name: "TrUAPIProvider", package: "truapi") +``` + +Release tags follow the repo-wide `@parity/ios-provider@` naming, which SPM's semver resolution does not consume — depend by `branch:` or `revision:` instead, as with `TrUAPIHost`. + +No Rust toolchain is needed: the xcframework carries the compiled crate, and the chain specs are compiled into it, so the app ships no spec files of its own and never refreshes them. Picking up a spec refresh means taking a newer release. + +## Public surface + +Everything is generated from [`ffi.rs`](../../rust/crates/truapi-provider/src/ffi.rs): + +- `ChainProvider` — construct **one per process** and share it. Every connection runs on the single embedded light client, so they share sync, peers, and warm state while keeping their own request queue and response stream. `connect(genesisHash:listener:)` resolves the network from the bundled catalog (relay wiring and statement-store placement included), so the 32-byte genesis hash is the only argument. +- `ChainMessageListener` — the host implements it; `onMessage(message:)` receives each JSON-RPC response and notification, `onClosed()` fires once the stream ends. +- `ChainConnection` — `send(request:)` queues a request, `close()` tears the connection down. +- `ChainProviderError` — `.connect(reason:)` when the genesis is outside the catalog or the transport fails, `.badGenesis` when the hash is not 32 bytes. + +## Architecture + +```text +host app + ChainProvider().connect(genesisHash:listener:) + | + v +libtruapi_provider (embedded smoldot + bundled chain-spec catalog) + → one light client per process, one added chain per connection + → responses pumped on a Rust-owned thread into ChainMessageListener +``` + +A connection is a raw JSON-RPC string pipe. The provider does no decoding: what smoldot answers is what the listener receives. + +## Example + +> **Threading:** the crate pumps each connection's responses on a background thread it owns, so `onMessage` and `onClosed` are never called on the main thread — hop to it before touching UIKit. `connect(genesisHash:listener:)` is synchronous and blocks the calling thread while the chain is added, so call it off the main thread. + +```swift +import Foundation +import TrUAPIProvider + +final class Responses: ChainMessageListener, @unchecked Sendable { + func onMessage(message: String) { + // A JSON-RPC response or subscription notification, verbatim from smoldot. + DispatchQueue.main.async { /* decode and render */ } + } + + func onClosed() { + DispatchQueue.main.async { /* the stream ended: drop the connection */ } + } +} + +// One provider per process; hold it for the app's lifetime. +let provider = ChainProvider() + +// 32 raw bytes, not a hex string. Must be a chain in the bundled catalog. +let genesis = Data(repeating: 0, count: 32) +let connection = try provider.connect(genesisHash: genesis, listener: Responses()) + +connection.send(request: #"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_genesisHash","params":[]}"#) + +// On teardown: +connection.close() +``` + +## Build outputs in detail + +`./scripts/rebuild.sh` orchestrates everything; the underlying pieces, should you need one in isolation: + +- **static libraries** — `cargo build -p truapi-provider --no-default-features --features uniffi` for `aarch64-apple-ios` and `aarch64-apple-ios-sim`. The `ws` backend is off, so the build carries the light client only. +- **bindings** — the workspace `uniffi-bindgen-cli` reads the built `libtruapi_provider.a` and emits `truapi_provider.swift` plus `truapi_providerFFI.h`/`.modulemap`. The script copies them into `Sources/`, renaming the emitted `truapi_providerFFI.modulemap` to `module.modulemap` so the SwiftPM `systemLibrary` target picks it up. +- **xcframework** — `xcodebuild -create-xcframework` bundles the slices with those same headers, and the result is copied into `Binaries/`. + +## Maintainers: cutting a release + +```bash +make provider-ios # must include the device slice +./ios/truapi-provider/scripts/publish.sh 0.1.0 +``` + +`publish.sh` creates the `@parity/ios-provider@` release if the tag does not exist yet (targeting `IOS_RELEASE_TARGET` when set, otherwise the current branch), uploads the zipped xcframework, and rewrites `providerBinaryURL` and `providerBinaryChecksum` in the root `Package.swift`. Commit that manifest change **after** the upload succeeds: a manifest pointing at an asset that is not live yet breaks every consumer resolving in that window. + +The provider and the host use separate tag namespaces, so releasing one never moves the other. diff --git a/ios/truapi-provider/Sources/TrUAPIProvider/truapi_provider.swift b/ios/truapi-provider/Sources/TrUAPIProvider/truapi_provider.swift new file mode 100644 index 000000000..2d285b4f6 --- /dev/null +++ b/ios/truapi-provider/Sources/TrUAPIProvider/truapi_provider.swift @@ -0,0 +1,1224 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +// swiftlint:disable all +import Foundation + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport(truapi_providerFFI) +import truapi_providerFFI +#endif + +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func empty() -> RustBuffer { + RustBuffer(capacity: 0, len:0, data: nil) + } + + static func from(_ ptr: UnsafeBufferPointer) -> RustBuffer { + try! rustCall { ffi_truapi_provider_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { ffi_truapi_provider_rustbuffer_free(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } + + init(rawBufferPointer: UnsafeRawBufferPointer) { + self.init( + len: Int32(rawBufferPointer.count), + data: rawBufferPointer.baseAddress?.assumingMemoryBound(to: UInt8.self) + ) + } +} + +// Converter for `&[u8]` / `[ByRef] bytes` arguments. +// +// Conforms to `FfiConverter` so the compiler enforces the full converter +// method set. Only the scope-bound `lower(_:_body:)` overload is sound — +// zero-copy byte buffers only flow foreign -> Rust, and only in argument +// position. The four protocol-witness methods (`lift`, `lower`, `read`, +// `write`) `fatalError` at runtime if anyone reaches them. +// +// The scope-bound `lower` takes a closure because the `ForeignBytes` +// pointer is only guaranteed valid for the duration of +// `Data.withUnsafeBytes`. Callers must run the full FFI call inside +// the closure body. +fileprivate enum FfiConverterByRefBytes: FfiConverter { + typealias SwiftType = Data + typealias FfiType = ForeignBytes + + static func lower(_ value: Data, _ body: (ForeignBytes) throws -> R) rethrows -> R { + return try value.withUnsafeBytes { rawBuf in + try body(ForeignBytes(rawBufferPointer: rawBuf)) + } + } + + static func lower(_ value: Data) -> ForeignBytes { + fatalError("ByRef bytes cannot use the plain lower: returning ForeignBytes escapes the Data.withUnsafeBytes scope. Use the scope-bound lower(_:_body:) overload instead.") + } + + static func lift(_ value: ForeignBytes) throws -> Data { + fatalError("ByRef bytes cannot be lifted: zero-copy &[u8] only flows foreign->Rust") + } + + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Data { + fatalError("ByRef bytes cannot be read from a buffer: zero-copy &[u8] is only supported in argument position, not nested in records/options/etc.") + } + + static func write(_ value: Data, into buf: inout [UInt8]) { + fatalError("ByRef bytes cannot be written to a buffer: zero-copy &[u8] is only supported in argument position, not nested in records/options/etc.") + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a library of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + self.init( + bytesNoCopy: rustBuffer.data!, + count: Int(rustBuffer.len), + deallocator: .none + ) + } +} + +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} + +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +fileprivate func readInt(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset...size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)}) + reader.offset = range.upperBound + return value.bigEndian +} + +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array { + let range = reader.offset..<(reader.offset+count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + reader.data.copyBytes(to: buffer, from: range) + }) + reader.offset = range.upperBound + return value +} + +// Reads a float at the current offset. +fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return Float(bitPattern: try readInt(&reader)) +} + +// Reads a float at the current offset. +fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return Double(bitPattern: try readInt(&reader)) +} + +// Indicates if the offset has reached the end of the buffer. +fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count +} + +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. + +fileprivate func createWriter() -> [UInt8] { + return [] +} + +fileprivate func writeBytes(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} + +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +fileprivate func writeInt(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} + +fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} + +fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous to the Rust trait of the same name. +fileprivate protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } + +extension FfiConverterPrimitive { +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lower(_ value: SwiftType) -> FfiType { + return value + } +} + +// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`. +// Used for complex types where it's hard to write a custom lift/lower. +fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lift(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lower(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate extension NSLock { + func withLock(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2 +fileprivate let CALL_CANCELLED: Int8 = 3 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + let neverThrow: ((RustBuffer) throws -> Never)? = nil + return try makeRustCall(callback, errorHandler: neverThrow) +} + +private func rustCallWithError( + _ errorHandler: @escaping (RustBuffer) throws -> E, + _ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: errorHandler) +} + +private func makeRustCall( + _ callback: (UnsafeMutablePointer) -> T, + errorHandler: ((RustBuffer) throws -> E)? +) throws -> T { + uniffiEnsureTruapiProviderInitialized() + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) + return returnedVal +} + +private func uniffiCheckCallStatus( + callStatus: RustCallStatus, + errorHandler: ((RustBuffer) throws -> E)? +) throws { + switch callStatus.code { + case CALL_SUCCESS: + return + + case CALL_ERROR: + if let errorHandler = errorHandler { + throw try errorHandler(callStatus.errorBuf) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.unexpectedRustCallError + } + + case CALL_UNEXPECTED_ERROR: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try FfiConverterString.lift(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + case CALL_CANCELLED: + fatalError("Cancellation not supported yet") + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} + +private func uniffiTraitInterfaceCall( + callStatus: UnsafeMutablePointer, + makeCall: () throws -> T, + writeReturn: (T) -> () +) { + do { + try writeReturn(makeCall()) + } catch let error { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) + } +} + +private func uniffiTraitInterfaceCallWithError( + callStatus: UnsafeMutablePointer, + makeCall: () throws -> T, + writeReturn: (T) -> (), + lowerError: (E) -> RustBuffer +) { + do { + try writeReturn(makeCall()) + } catch let error as E { + callStatus.pointee.code = CALL_ERROR + callStatus.pointee.errorBuf = lowerError(error) + } catch { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) + } +} +// Initial value and increment amount for handles. +// These ensure that SWIFT handles always have the lowest bit set +fileprivate let UNIFFI_HANDLEMAP_INITIAL: UInt64 = 1 +fileprivate let UNIFFI_HANDLEMAP_DELTA: UInt64 = 2 + +fileprivate final class UniffiHandleMap: @unchecked Sendable { + // All mutation happens with this lock held, which is why we implement @unchecked Sendable. + private let lock = NSLock() + private var map: [UInt64: T] = [:] + private var currentHandle: UInt64 = UNIFFI_HANDLEMAP_INITIAL + + func insert(obj: T) -> UInt64 { + lock.withLock { + return doInsert(obj) + } + } + + // Low-level insert function, this assumes `lock` is held. + private func doInsert(_ obj: T) -> UInt64 { + let handle = currentHandle + currentHandle += UNIFFI_HANDLEMAP_DELTA + map[handle] = obj + return handle + } + + func get(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map[handle] else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + func clone(handle: UInt64) throws -> UInt64 { + try lock.withLock { + guard let obj = map[handle] else { + throw UniffiInternalError.unexpectedStaleHandle + } + return doInsert(obj) + } + } + + @discardableResult + func remove(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map.removeValue(forKey: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + var count: Int { + get { + map.count + } + } +} + + +// Public interface members begin here. +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +private let IDX_CALLBACK_FREE: Int32 = 0 +// Callback return codes +private let UNIFFI_CALLBACK_SUCCESS: Int32 = 0 +private let UNIFFI_CALLBACK_ERROR: Int32 = 1 +private let UNIFFI_CALLBACK_UNEXPECTED_ERROR: Int32 = 2 + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + public static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer(start: value.data!, count: Int(value.len)) + // Use Swift's native UTF-8 decoder; `String(bytes:encoding:.utf8)` goes + // through Foundation's NSString and silently strips a leading U+FEFF BOM. + // Invalid UTF-8 substitutes U+FFFD instead of trapping (unreachable + // given Rust's `String` invariant). + return String(decoding: bytes, as: UTF8.self) + } + + public static func lower(_ value: String) -> RustBuffer { + return value.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + // See `lift` above for why we avoid Foundation's NSString-backed decoder here. + return String(decoding: try readBytes(&buf, count: Int(len)), as: UTF8.self) + } + + public static func write(_ value: String, into buf: inout [UInt8]) { + let len = Int32(value.utf8.count) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterData: FfiConverterRustBuffer { + typealias SwiftType = Data + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Data { + let len: Int32 = try readInt(&buf) + return Data(try readBytes(&buf, count: Int(len))) + } + + public static func write(_ value: Data, into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + writeBytes(&buf, value) + } +} + + + + +/** + * A live JSON-RPC connection: a raw string pipe to one chain. + */ +public protocol ChainConnectionProtocol: AnyObject, Sendable { + + /** + * Close the connection; the listener's `on_closed` fires once the stream + * ends. + */ + func close() + + /** + * Queue a JSON-RPC request string. + */ + func send(request: String) + +} +/** + * A live JSON-RPC connection: a raw string pipe to one chain. + */ +open class ChainConnection: ChainConnectionProtocol, @unchecked Sendable { + fileprivate let handle: UInt64 + + /// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public struct NoHandle { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + required public init(unsafeFromHandle handle: UInt64) { + self.handle = handle + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public init(noHandle: NoHandle) { + self.handle = 0 + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public func uniffiCloneHandle() -> UInt64 { + return try! rustCall { uniffi_truapi_provider_fn_clone_chainconnection(self.handle, $0) } + } + // No primary constructor declared for this class. + + deinit { + if handle == 0 { + // Mock objects have handle=0 don't try to free them + return + } + + try! rustCall { uniffi_truapi_provider_fn_free_chainconnection(handle, $0) } + } + + + + + /** + * Close the connection; the listener's `on_closed` fires once the stream + * ends. + */ +open func close() {try! rustCall() { + uniffiCallStatus in + uniffi_truapi_provider_fn_method_chainconnection_close( + self.uniffiCloneHandle(),uniffiCallStatus + ) +} +} + + /** + * Queue a JSON-RPC request string. + */ +open func send(request: String) {try! rustCall() { + uniffiCallStatus in + uniffi_truapi_provider_fn_method_chainconnection_send( + self.uniffiCloneHandle(), + FfiConverterString.lower(request),uniffiCallStatus + ) +} +} + + + +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeChainConnection: FfiConverter { + typealias FfiType = UInt64 + typealias SwiftType = ChainConnection + + public static func lift(_ handle: UInt64) throws -> ChainConnection { + return ChainConnection(unsafeFromHandle: handle) + } + + public static func lower(_ value: ChainConnection) -> UInt64 { + return value.uniffiCloneHandle() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChainConnection { + let handle: UInt64 = try readInt(&buf) + return try lift(handle) + } + + public static func write(_ value: ChainConnection, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChainConnection_lift(_ handle: UInt64) throws -> ChainConnection { + return try FfiConverterTypeChainConnection.lift(handle) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChainConnection_lower(_ value: ChainConnection) -> UInt64 { + return FfiConverterTypeChainConnection.lower(value) +} + + + + + + +/** + * Sink for a connection's inbound JSON-RPC responses and notifications, + * implemented on the foreign (Swift) side. + */ +public protocol ChainMessageListener: AnyObject, Sendable { + + /** + * Called for each JSON-RPC response or notification string. + */ + func onMessage(message: String) + + /** + * Called once the connection's response stream ends. + */ + func onClosed() + +} +/** + * Sink for a connection's inbound JSON-RPC responses and notifications, + * implemented on the foreign (Swift) side. + */ +open class ChainMessageListenerImpl: ChainMessageListener, @unchecked Sendable { + fileprivate let handle: UInt64 + + /// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public struct NoHandle { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + required public init(unsafeFromHandle handle: UInt64) { + self.handle = handle + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public init(noHandle: NoHandle) { + self.handle = 0 + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public func uniffiCloneHandle() -> UInt64 { + return try! rustCall { uniffi_truapi_provider_fn_clone_chainmessagelistener(self.handle, $0) } + } + // No primary constructor declared for this class. + + deinit { + if handle == 0 { + // Mock objects have handle=0 don't try to free them + return + } + + try! rustCall { uniffi_truapi_provider_fn_free_chainmessagelistener(handle, $0) } + } + + + + + /** + * Called for each JSON-RPC response or notification string. + */ +open func onMessage(message: String) {try! rustCall() { + uniffiCallStatus in + uniffi_truapi_provider_fn_method_chainmessagelistener_on_message( + self.uniffiCloneHandle(), + FfiConverterString.lower(message),uniffiCallStatus + ) +} +} + + /** + * Called once the connection's response stream ends. + */ +open func onClosed() {try! rustCall() { + uniffiCallStatus in + uniffi_truapi_provider_fn_method_chainmessagelistener_on_closed( + self.uniffiCloneHandle(),uniffiCallStatus + ) +} +} + + + +} + + + +// Put the implementation in a struct so we don't pollute the top-level namespace +fileprivate struct UniffiCallbackInterfaceChainMessageListener { + + // Create the VTable using a series of closures. + // Swift automatically converts these into C callback functions. + // + // Store the vtable directly. + static let vtable: UniffiVTableCallbackInterfaceChainMessageListener = UniffiVTableCallbackInterfaceChainMessageListener( + uniffiFree: { (uniffiHandle: UInt64) -> () in + do { + try FfiConverterTypeChainMessageListener.handleMap.remove(handle: uniffiHandle) + } catch { + print("Uniffi callback interface ChainMessageListener: handle missing in uniffiFree") + } + }, + uniffiClone: { (uniffiHandle: UInt64) -> UInt64 in + do { + return try FfiConverterTypeChainMessageListener.handleMap.clone(handle: uniffiHandle) + } catch { + fatalError("Uniffi callback interface ChainMessageListener: handle missing in uniffiClone") + } + }, + onMessage: { ( + uniffiHandle: UInt64, + message: RustBuffer, + uniffiOutReturn: UnsafeMutableRawPointer, + uniffiCallStatus: UnsafeMutablePointer + ) in + let makeCall = { + () throws -> () in + guard let uniffiObj = try? FfiConverterTypeChainMessageListener.handleMap.get(handle: uniffiHandle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return uniffiObj.onMessage( + message: try FfiConverterString.lift(message) + ) + } + + + let writeReturn = { () } + uniffiTraitInterfaceCall( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn + ) + }, + onClosed: { ( + uniffiHandle: UInt64, + uniffiOutReturn: UnsafeMutableRawPointer, + uniffiCallStatus: UnsafeMutablePointer + ) in + let makeCall = { + () throws -> () in + guard let uniffiObj = try? FfiConverterTypeChainMessageListener.handleMap.get(handle: uniffiHandle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return uniffiObj.onClosed( + ) + } + + + let writeReturn = { () } + uniffiTraitInterfaceCall( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn + ) + } + ) + + // Rust stores this pointer for future callback invocations, so it must live + // for the process lifetime (not just for the init function call). + // + // `nonisolated(unsafe)` is needed under Swift 6 strict concurrency. + // This is safe because the pointee is initialized once during static init + // and never mutated by either side of the FFI. Its fields are C function pointers. + nonisolated(unsafe) static let vtablePtr: UnsafePointer = { + let ptr = UnsafeMutablePointer.allocate(capacity: 1) + ptr.initialize(to: vtable) + return UnsafePointer(ptr) + }() +} + +private func uniffiCallbackInitChainMessageListener() { + uniffi_truapi_provider_fn_init_callback_vtable_chainmessagelistener(UniffiCallbackInterfaceChainMessageListener.vtablePtr) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeChainMessageListener: FfiConverter { + fileprivate static let handleMap = UniffiHandleMap() + + typealias FfiType = UInt64 + typealias SwiftType = ChainMessageListener + + public static func lift(_ handle: UInt64) throws -> ChainMessageListener { + if ((handle & 1) == 0) { + // Rust-generated handle, construct a new class that uses the handle to implement the + // interface + return ChainMessageListenerImpl(unsafeFromHandle: handle) + } else { + // Swift-generated handle, get the object from the handle map + return try handleMap.remove(handle: handle) + } + } + + public static func lower(_ value: ChainMessageListener) -> UInt64 { + if let rustImpl = value as? ChainMessageListenerImpl { + // Rust-implemented object. Clone the handle and return it + return rustImpl.uniffiCloneHandle() + } else { + // Swift object, generate a new vtable handle and return that. + return handleMap.insert(obj: value) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChainMessageListener { + let handle: UInt64 = try readInt(&buf) + return try lift(handle) + } + + public static func write(_ value: ChainMessageListener, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChainMessageListener_lift(_ handle: UInt64) throws -> ChainMessageListener { + return try FfiConverterTypeChainMessageListener.lift(handle) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChainMessageListener_lower(_ value: ChainMessageListener) -> UInt64 { + return FfiConverterTypeChainMessageListener.lower(value) +} + + + + + + +/** + * Embedded-smoldot chain provider. Construct one per process and share it; + * every connection runs on the single embedded light client. + */ +public protocol ChainProviderProtocol: AnyObject, Sendable { + + /** + * Open a connection to the chain identified by `genesis_hash` (32 bytes). + * The network is resolved from the catalog; responses are delivered to + * `listener` until the connection closes. + */ + func connect(genesisHash: Data, listener: ChainMessageListener) throws -> ChainConnection + +} +/** + * Embedded-smoldot chain provider. Construct one per process and share it; + * every connection runs on the single embedded light client. + */ +open class ChainProvider: ChainProviderProtocol, @unchecked Sendable { + fileprivate let handle: UInt64 + + /// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public struct NoHandle { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + required public init(unsafeFromHandle handle: UInt64) { + self.handle = handle + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public init(noHandle: NoHandle) { + self.handle = 0 + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public func uniffiCloneHandle() -> UInt64 { + return try! rustCall { uniffi_truapi_provider_fn_clone_chainprovider(self.handle, $0) } + } + /** + * Create a provider backed by the bundled network catalog. + */ +public convenience init() { + let handle = + try! rustCall() { + uniffiCallStatus in + uniffi_truapi_provider_fn_constructor_chainprovider_new(uniffiCallStatus + ) +} + self.init(unsafeFromHandle: handle) +} + + deinit { + if handle == 0 { + // Mock objects have handle=0 don't try to free them + return + } + + try! rustCall { uniffi_truapi_provider_fn_free_chainprovider(handle, $0) } + } + + + + + /** + * Open a connection to the chain identified by `genesis_hash` (32 bytes). + * The network is resolved from the catalog; responses are delivered to + * `listener` until the connection closes. + */ +open func connect(genesisHash: Data, listener: ChainMessageListener)throws -> ChainConnection { + return try FfiConverterTypeChainConnection_lift(try rustCallWithError(FfiConverterTypeChainProviderError_lift) { + uniffiCallStatus in + uniffi_truapi_provider_fn_method_chainprovider_connect( + self.uniffiCloneHandle(), + FfiConverterData.lower(genesisHash), + FfiConverterTypeChainMessageListener_lower(listener),uniffiCallStatus + ) +}) +} + + + +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeChainProvider: FfiConverter { + typealias FfiType = UInt64 + typealias SwiftType = ChainProvider + + public static func lift(_ handle: UInt64) throws -> ChainProvider { + return ChainProvider(unsafeFromHandle: handle) + } + + public static func lower(_ value: ChainProvider) -> UInt64 { + return value.uniffiCloneHandle() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChainProvider { + let handle: UInt64 = try readInt(&buf) + return try lift(handle) + } + + public static func write(_ value: ChainProvider, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChainProvider_lift(_ handle: UInt64) throws -> ChainProvider { + return try FfiConverterTypeChainProvider.lift(handle) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChainProvider_lower(_ value: ChainProvider) -> UInt64 { + return FfiConverterTypeChainProvider.lower(value) +} + + + + +/** + * Errors surfaced to the foreign caller. + */ +public +enum ChainProviderError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError { + + + + /** + * The chain could not be connected (unknown genesis, transport failure). + */ + case Connect( + /** + * Human-readable failure reason. + */reason: String + ) + /** + * The genesis hash was not exactly 32 bytes. + */ + case BadGenesis + + + + + + + public var errorDescription: String? { + String(reflecting: self) + } + +} + +#if compiler(>=6) +extension ChainProviderError: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeChainProviderError: FfiConverterRustBuffer { + typealias SwiftType = ChainProviderError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ChainProviderError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .Connect( + reason: try FfiConverterString.read(from: &buf) + ) + case 2: return .BadGenesis + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: ChainProviderError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .Connect(reason): + writeInt(&buf, Int32(1)) + FfiConverterString.write(reason, into: &buf) + + + case .BadGenesis: + writeInt(&buf, Int32(2)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChainProviderError_lift(_ buf: RustBuffer) throws -> ChainProviderError { + return try FfiConverterTypeChainProviderError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeChainProviderError_lower(_ value: ChainProviderError) -> RustBuffer { + return FfiConverterTypeChainProviderError.lower(value) +} + +private enum InitializationResult { + case ok + case contractVersionMismatch + case apiChecksumMismatch +} +// Use a global variable to perform the versioning checks. Swift ensures that +// the code inside is only computed once. +private let initializationResult: InitializationResult = { + // Get the bindings contract version from our ComponentInterface + let bindings_contract_version = 30 + // Get the scaffolding contract version by calling the into the dylib + let scaffolding_contract_version = ffi_truapi_provider_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version { + return InitializationResult.contractVersionMismatch + } + if (uniffi_truapi_provider_checksum_method_chainconnection_close() != 23404) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_truapi_provider_checksum_method_chainconnection_send() != 52883) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_truapi_provider_checksum_method_chainmessagelistener_on_message() != 65048) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_truapi_provider_checksum_method_chainmessagelistener_on_closed() != 39748) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_truapi_provider_checksum_method_chainprovider_connect() != 16550) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_truapi_provider_checksum_constructor_chainprovider_new() != 37203) { + return InitializationResult.apiChecksumMismatch + } + + uniffiCallbackInitChainMessageListener() + return InitializationResult.ok +}() + +// Make the ensure init function public so that other modules which have external type references to +// our types can call it. +public func uniffiEnsureTruapiProviderInitialized() { + switch initializationResult { + case .ok: + break + case .contractVersionMismatch: + fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + case .apiChecksumMismatch: + fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +// swiftlint:enable all \ No newline at end of file diff --git a/ios/truapi-provider/Sources/truapi_providerFFI/include/module.modulemap b/ios/truapi-provider/Sources/truapi_providerFFI/include/module.modulemap new file mode 100644 index 000000000..cddb60daa --- /dev/null +++ b/ios/truapi-provider/Sources/truapi_providerFFI/include/module.modulemap @@ -0,0 +1,7 @@ +module truapi_providerFFI { + header "truapi_providerFFI.h" + export * + use "Darwin" + use "_Builtin_stdbool" + use "_Builtin_stdint" +} \ No newline at end of file diff --git a/ios/truapi-provider/Sources/truapi_providerFFI/include/truapi_providerFFI.h b/ios/truapi-provider/Sources/truapi_providerFFI/include/truapi_providerFFI.h new file mode 100644 index 000000000..d886cf36e --- /dev/null +++ b/ios/truapi-provider/Sources/truapi_providerFFI/include/truapi_providerFFI.h @@ -0,0 +1,638 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + uint64_t capacity; + uint64_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK +typedef void (*UniffiForeignFutureDroppedCallback)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE +typedef uint64_t (*UniffiCallbackInterfaceClone)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT +typedef struct UniffiForeignFutureDroppedCallbackStruct { + uint64_t handle; + UniffiForeignFutureDroppedCallback _Nonnull free; +} UniffiForeignFutureDroppedCallbackStruct; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8 +typedef struct UniffiForeignFutureResultU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureResultU8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8 +typedef struct UniffiForeignFutureResultI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureResultI8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16 +typedef struct UniffiForeignFutureResultU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureResultU16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16 +typedef struct UniffiForeignFutureResultI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureResultI16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32 +typedef struct UniffiForeignFutureResultU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureResultU32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32 +typedef struct UniffiForeignFutureResultI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureResultI32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64 +typedef struct UniffiForeignFutureResultU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureResultU64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64 +typedef struct UniffiForeignFutureResultI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureResultI64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32 +typedef struct UniffiForeignFutureResultF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureResultF32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64 +typedef struct UniffiForeignFutureResultF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureResultF64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER +typedef struct UniffiForeignFutureResultRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureResultRustBuffer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID +typedef struct UniffiForeignFutureResultVoid { + RustCallStatus callStatus; +} UniffiForeignFutureResultVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CHAIN_MESSAGE_LISTENER_METHOD0 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CHAIN_MESSAGE_LISTENER_METHOD0 +typedef void (*UniffiCallbackInterfaceChainMessageListenerMethod0)(uint64_t, RustBuffer, void* _Nonnull, + RustCallStatus *_Nonnull uniffiCallStatus + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CHAIN_MESSAGE_LISTENER_METHOD1 +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CHAIN_MESSAGE_LISTENER_METHOD1 +typedef void (*UniffiCallbackInterfaceChainMessageListenerMethod1)(uint64_t, void* _Nonnull, + RustCallStatus *_Nonnull uniffiCallStatus + ); + +#endif +#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_CHAIN_MESSAGE_LISTENER +#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_CHAIN_MESSAGE_LISTENER +typedef struct UniffiVTableCallbackInterfaceChainMessageListener { + UniffiCallbackInterfaceFree _Nonnull uniffiFree; + UniffiCallbackInterfaceClone _Nonnull uniffiClone; + UniffiCallbackInterfaceChainMessageListenerMethod0 _Nonnull onMessage; + UniffiCallbackInterfaceChainMessageListenerMethod1 _Nonnull onClosed; +} UniffiVTableCallbackInterfaceChainMessageListener; + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_CLONE_CHAINCONNECTION +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_CLONE_CHAINCONNECTION +uint64_t uniffi_truapi_provider_fn_clone_chainconnection(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_FREE_CHAINCONNECTION +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_FREE_CHAINCONNECTION +void uniffi_truapi_provider_fn_free_chainconnection(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINCONNECTION_CLOSE +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINCONNECTION_CLOSE +void uniffi_truapi_provider_fn_method_chainconnection_close(uint64_t ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINCONNECTION_SEND +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINCONNECTION_SEND +void uniffi_truapi_provider_fn_method_chainconnection_send(uint64_t ptr, RustBuffer request, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_CLONE_CHAINMESSAGELISTENER +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_CLONE_CHAINMESSAGELISTENER +uint64_t uniffi_truapi_provider_fn_clone_chainmessagelistener(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_FREE_CHAINMESSAGELISTENER +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_FREE_CHAINMESSAGELISTENER +void uniffi_truapi_provider_fn_free_chainmessagelistener(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_INIT_CALLBACK_VTABLE_CHAINMESSAGELISTENER +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_INIT_CALLBACK_VTABLE_CHAINMESSAGELISTENER +void uniffi_truapi_provider_fn_init_callback_vtable_chainmessagelistener(const UniffiVTableCallbackInterfaceChainMessageListener* _Nonnull vtable +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINMESSAGELISTENER_ON_MESSAGE +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINMESSAGELISTENER_ON_MESSAGE +void uniffi_truapi_provider_fn_method_chainmessagelistener_on_message(uint64_t ptr, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINMESSAGELISTENER_ON_CLOSED +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINMESSAGELISTENER_ON_CLOSED +void uniffi_truapi_provider_fn_method_chainmessagelistener_on_closed(uint64_t ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_CLONE_CHAINPROVIDER +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_CLONE_CHAINPROVIDER +uint64_t uniffi_truapi_provider_fn_clone_chainprovider(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_FREE_CHAINPROVIDER +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_FREE_CHAINPROVIDER +void uniffi_truapi_provider_fn_free_chainprovider(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_CONSTRUCTOR_CHAINPROVIDER_NEW +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_CONSTRUCTOR_CHAINPROVIDER_NEW +uint64_t uniffi_truapi_provider_fn_constructor_chainprovider_new(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINPROVIDER_CONNECT +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_FN_METHOD_CHAINPROVIDER_CONNECT +uint64_t uniffi_truapi_provider_fn_method_chainprovider_connect(uint64_t ptr, RustBuffer genesis_hash, uint64_t listener, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUSTBUFFER_ALLOC +RustBuffer ffi_truapi_provider_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_truapi_provider_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUSTBUFFER_FREE +void ffi_truapi_provider_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUSTBUFFER_RESERVE +RustBuffer ffi_truapi_provider_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_U8 +void ffi_truapi_provider_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_U8 +void ffi_truapi_provider_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_U8 +void ffi_truapi_provider_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_truapi_provider_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_I8 +void ffi_truapi_provider_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_I8 +void ffi_truapi_provider_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_I8 +void ffi_truapi_provider_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_truapi_provider_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_U16 +void ffi_truapi_provider_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_U16 +void ffi_truapi_provider_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_U16 +void ffi_truapi_provider_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_truapi_provider_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_I16 +void ffi_truapi_provider_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_I16 +void ffi_truapi_provider_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_I16 +void ffi_truapi_provider_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_truapi_provider_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_U32 +void ffi_truapi_provider_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_U32 +void ffi_truapi_provider_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_U32 +void ffi_truapi_provider_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_truapi_provider_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_I32 +void ffi_truapi_provider_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_I32 +void ffi_truapi_provider_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_I32 +void ffi_truapi_provider_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_truapi_provider_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_U64 +void ffi_truapi_provider_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_U64 +void ffi_truapi_provider_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_U64 +void ffi_truapi_provider_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_truapi_provider_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_I64 +void ffi_truapi_provider_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_I64 +void ffi_truapi_provider_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_I64 +void ffi_truapi_provider_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_truapi_provider_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_F32 +void ffi_truapi_provider_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_F32 +void ffi_truapi_provider_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_F32 +void ffi_truapi_provider_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_F32 +float ffi_truapi_provider_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_F64 +void ffi_truapi_provider_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_F64 +void ffi_truapi_provider_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_F64 +void ffi_truapi_provider_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_F64 +double ffi_truapi_provider_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_truapi_provider_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_truapi_provider_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_truapi_provider_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_truapi_provider_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_POLL_VOID +void ffi_truapi_provider_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_CANCEL_VOID +void ffi_truapi_provider_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_FREE_VOID +void ffi_truapi_provider_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_RUST_FUTURE_COMPLETE_VOID +void ffi_truapi_provider_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINCONNECTION_CLOSE +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINCONNECTION_CLOSE +uint16_t uniffi_truapi_provider_checksum_method_chainconnection_close(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINCONNECTION_SEND +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINCONNECTION_SEND +uint16_t uniffi_truapi_provider_checksum_method_chainconnection_send(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINMESSAGELISTENER_ON_MESSAGE +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINMESSAGELISTENER_ON_MESSAGE +uint16_t uniffi_truapi_provider_checksum_method_chainmessagelistener_on_message(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINMESSAGELISTENER_ON_CLOSED +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINMESSAGELISTENER_ON_CLOSED +uint16_t uniffi_truapi_provider_checksum_method_chainmessagelistener_on_closed(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINPROVIDER_CONNECT +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_METHOD_CHAINPROVIDER_CONNECT +uint16_t uniffi_truapi_provider_checksum_method_chainprovider_connect(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_CONSTRUCTOR_CHAINPROVIDER_NEW +#define UNIFFI_FFIDEF_UNIFFI_TRUAPI_PROVIDER_CHECKSUM_CONSTRUCTOR_CHAINPROVIDER_NEW +uint16_t uniffi_truapi_provider_checksum_constructor_chainprovider_new(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_TRUAPI_PROVIDER_UNIFFI_CONTRACT_VERSION +uint32_t ffi_truapi_provider_uniffi_contract_version(void + +); +#endif + diff --git a/ios/truapi-provider/scripts/publish.sh b/ios/truapi-provider/scripts/publish.sh new file mode 100755 index 000000000..bc7377918 --- /dev/null +++ b/ios/truapi-provider/scripts/publish.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# Publish the locally built truapi_provider.xcframework as a GitHub release asset +# and point the root Package.swift at it (URL + checksum). +# +# Build first with scripts/rebuild.sh, then: +# ./scripts/publish.sh e.g. ./scripts/publish.sh 0.1.0 +# +# Tag "@parity/ios-provider@", title "@parity/ios-provider ", +# a namespace of its own so the provider and the host release independently. +# Creates the release if the tag does not exist yet (targeting +# IOS_RELEASE_TARGET when set, or the current branch), otherwise replaces the +# asset on the existing release. Commit the resulting Package.swift change AFTER +# the upload succeeds — a manifest pushed before its asset is live breaks every +# consumer resolving in that window. +set -eu + +if [ $# -ne 1 ]; then + echo "usage: $0 " >&2 + exit 64 +fi + +VERSION="$1" +TAG="@parity/ios-provider@${VERSION}" +TITLE="@parity/ios-provider ${VERSION}" +PACKAGE_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +TRUAPI_ROOT="$(cd "$PACKAGE_ROOT/../.." && pwd)" +XCFRAMEWORK="$PACKAGE_ROOT/Binaries/truapi_provider.xcframework" +BRANCH="$(git -C "$TRUAPI_ROOT" rev-parse --abbrev-ref HEAD)" +RELEASE_TARGET="${IOS_RELEASE_TARGET:-$BRANCH}" + +if [ "$BRANCH" = "HEAD" ] && [ -z "${IOS_RELEASE_TARGET:-}" ]; then + echo "error: detached HEAD — check out the branch or set IOS_RELEASE_TARGET" >&2 + exit 65 +fi + +if [ ! -d "$XCFRAMEWORK" ]; then + echo "error: $XCFRAMEWORK not found — run scripts/rebuild.sh first" >&2 + exit 66 +fi + +# A device slice is required for anything that ships; a simulator-only build is +# for local iteration and must not become a release asset. +if [ ! -d "$XCFRAMEWORK/ios-arm64" ]; then + echo "error: $XCFRAMEWORK has no device slice — rerun scripts/rebuild.sh without --sim-only" >&2 + exit 66 +fi + +if ! git -C "$TRUAPI_ROOT" diff --quiet -- Package.swift; then + echo "error: Package.swift has uncommitted changes — commit or revert them first" >&2 + exit 65 +fi + +STAGING="$(mktemp -d)" +ZIP="$STAGING/truapi_provider.xcframework.zip" +trap 'rm -rf "$STAGING"' EXIT + +ditto -c -k --keepParent "$XCFRAMEWORK" "$ZIP" +CHECKSUM="$(cd "$TRUAPI_ROOT" && swift package compute-checksum "$ZIP")" + +if gh release view "$TAG" --repo paritytech/truapi >/dev/null 2>&1; then + gh release upload "$TAG" "$ZIP" --repo paritytech/truapi --clobber +else + gh release create "$TAG" "$ZIP" \ + --repo paritytech/truapi \ + --target "$RELEASE_TARGET" \ + --title "$TITLE" \ + --latest=false \ + --notes "truapi_provider.xcframework for the TrUAPIProvider Swift package." +fi + +# The tag contains "@" and "/" — percent-encode it for the asset URL. +ENCODED_TAG="$(printf %s "$TAG" | sed 's/@/%40/g; s,/,%2F,g')" +URL="https://github.com/paritytech/truapi/releases/download/${ENCODED_TAG}/truapi_provider.xcframework.zip" +MANIFEST="$TRUAPI_ROOT/Package.swift" +sed -i '' -E "s|^let providerBinaryURL = .*|let providerBinaryURL = \"$URL\"|" "$MANIFEST" +sed -i '' -E "s|^let providerBinaryChecksum = .*|let providerBinaryChecksum = \"$CHECKSUM\"|" "$MANIFEST" + +echo "Published $TAG ($CHECKSUM)" +echo "Package.swift updated — review and commit it." diff --git a/ios/truapi-provider/scripts/rebuild.sh b/ios/truapi-provider/scripts/rebuild.sh new file mode 100755 index 000000000..35410ec62 --- /dev/null +++ b/ios/truapi-provider/scripts/rebuild.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Regenerate the TrUAPIProvider package build outputs in place: +# * truapi_provider.xcframework (Binaries/), device and simulator slices +# * uniffi-generated Swift bindings +# (Sources/TrUAPIProvider + Sources/truapi_providerFFI) +# +# Run after changing the crate's uniffi surface or refreshing the bundled chain +# specs, and commit the regenerated bindings with the source change. +# Usage: ./scripts/rebuild.sh [--sim-only] +# +# --sim-only drops the device slice for a faster loop; publish.sh rejects the +# result, since a release asset without a device slice cannot ship. +set -euo pipefail + +PACKAGE_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +TRUAPI_ROOT="$(cd "$PACKAGE_ROOT/../.." && pwd)" +cd "$TRUAPI_ROOT" + +PROFILE="${PROFILE:-release}" +LIB=libtruapi_provider.a + +SLICES=(aarch64-apple-ios-sim aarch64-apple-ios) +[ "${1:-}" = "--sim-only" ] && SLICES=(aarch64-apple-ios-sim) + +CARGO_FLAGS=(-p truapi-provider --lib --no-default-features --features uniffi) +[ "$PROFILE" = release ] && CARGO_FLAGS+=(--release) + +for target in "${SLICES[@]}"; do + rustup target add "$target" >/dev/null 2>&1 || true + echo "==> building $target ($PROFILE)" + cargo build "${CARGO_FLAGS[@]}" --target "$target" +done + +# Bindings come from the workspace bindgen so every package in this repo +# generates them the same way. +UNIFFI_OUT="$TRUAPI_ROOT/target/uniffi-provider-swift-out" +rm -rf "$UNIFFI_OUT" && mkdir -p "$UNIFFI_OUT" +echo "==> generating Swift bindings" +cargo run -q -p uniffi-bindgen-cli -- generate \ + --library "target/${SLICES[0]}/$PROFILE/$LIB" \ + --language swift \ + --out-dir "$UNIFFI_OUT" + +mkdir -p "$PACKAGE_ROOT/Sources/TrUAPIProvider" \ + "$PACKAGE_ROOT/Sources/truapi_providerFFI/include" +cp "$UNIFFI_OUT/truapi_provider.swift" \ + "$PACKAGE_ROOT/Sources/TrUAPIProvider/truapi_provider.swift" +cp "$UNIFFI_OUT/truapi_providerFFI.h" \ + "$PACKAGE_ROOT/Sources/truapi_providerFFI/include/truapi_providerFFI.h" +cp "$UNIFFI_OUT/truapi_providerFFI.modulemap" \ + "$PACKAGE_ROOT/Sources/truapi_providerFFI/include/module.modulemap" + +# The xcframework carries the headers; the systemLibrary target above reads the +# committed copies, so both must come from this same generation. +HEADERS="$TRUAPI_ROOT/target/uniffi-provider-headers" +rm -rf "$HEADERS" && mkdir -p "$HEADERS" +cp "$UNIFFI_OUT/truapi_providerFFI.h" "$HEADERS/" +cp "$UNIFFI_OUT/truapi_providerFFI.modulemap" "$HEADERS/module.modulemap" + +OUT="$TRUAPI_ROOT/target/truapi_provider.xcframework" +rm -rf "$OUT" +ARGS=() +for target in "${SLICES[@]}"; do + ARGS+=(-library "$TRUAPI_ROOT/target/$target/$PROFILE/$LIB" -headers "$HEADERS") +done +echo "==> packaging truapi_provider.xcframework" +xcodebuild -create-xcframework "${ARGS[@]}" -output "$OUT" + +mkdir -p "$PACKAGE_ROOT/Binaries" +rm -rf "$PACKAGE_ROOT/Binaries/truapi_provider.xcframework" +cp -R "$OUT" "$PACKAGE_ROOT/Binaries/" + +echo "done." +echo "Build against it with TRUAPI_USE_LOCAL_BINARY=1; publish with scripts/publish.sh." diff --git a/js/packages/truapi-provider/.gitignore b/js/packages/truapi-provider/.gitignore new file mode 100644 index 000000000..96b3dea0f --- /dev/null +++ b/js/packages/truapi-provider/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +# Generated wasm-pack bundle; rebuilt with `npm run build:wasm`. +dist/ diff --git a/js/packages/truapi-provider/LICENSE b/js/packages/truapi-provider/LICENSE new file mode 100644 index 000000000..ad207e8ab --- /dev/null +++ b/js/packages/truapi-provider/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Parity Technologies + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/js/packages/truapi-provider/LICENSE-APACHE b/js/packages/truapi-provider/LICENSE-APACHE new file mode 100644 index 000000000..834d9d9c1 --- /dev/null +++ b/js/packages/truapi-provider/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative + Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/js/packages/truapi-provider/NOTICE b/js/packages/truapi-provider/NOTICE new file mode 100644 index 000000000..69b092a94 --- /dev/null +++ b/js/packages/truapi-provider/NOTICE @@ -0,0 +1,19 @@ +@parity/truapi-provider + +This package ships the `truapi-provider` Rust crate compiled to WebAssembly. It +is licensed MIT (see LICENSE) except for the vendored browser light-client +platform, which is licensed Apache-2.0 (see LICENSE-APACHE). + +Vendored components (compiled into truapi_provider_bg.wasm) +---------------------------------------------------------- + +The crate's src/light_platform_web/{platform,helpers,socket}.rs + Copyright 2019-2026 Parity Technologies (UK) Ltd. + Vendored from subxt-lightclient 0.50.1 (github.com/paritytech/subxt, + crates/lightclient), which is dual-licensed Apache-2.0 OR GPL-3.0; used here + under Apache-2.0. The `onmessage` handler in socket.rs was modified to ignore + non-ArrayBuffer frames instead of panicking; otherwise the copy is faithful. + +The embedded light client itself (the `smoldot` / `smoldot-light` crates, +GPL-3.0-or-later WITH Classpath-exception-2.0) is an ordinary Cargo dependency, +linked under the Classpath exception; its source is not vendored here. diff --git a/js/packages/truapi-provider/README.md b/js/packages/truapi-provider/README.md new file mode 100644 index 000000000..02cc7fa36 --- /dev/null +++ b/js/packages/truapi-provider/README.md @@ -0,0 +1,75 @@ +# @parity/truapi-provider + +Network transport for the TrUAPI `ChainProvider` contract: an embedded +[smoldot](https://github.com/smol-dot/smoldot) light client, or a remote +WebSocket JSON-RPC node, behind one API. This package is the WebAssembly build, +for browser and desktop (webview) hosts. + +You connect to a network by its genesis hash, and everything else is handled for +you: the bundled catalog provides the spec and relay wiring, so clients never +ship or refresh specs of their own. One light client is shared across all +connections. Its synced state can be captured and restored, so a launch resumes +from finalized state instead of syncing from scratch. + +## Usage + +Build a provider, connect to a network by its genesis hash, and exchange JSON-RPC +request and response strings over the connection. Every connection shares the one +embedded light client. + +#### Web (JavaScript) + +The published package is `wasm-bindgen` glue plus a `.wasm` binary. Instantiate +the module once per page or worker. + +```js +import init, { ChainProviderBuilder } from "@parity/truapi-provider"; +import wasmUrl from "@parity/truapi-provider/truapi_provider_bg.wasm?url"; + +await init({ module_or_path: wasmUrl }); + +const genesis = "0x3740…"; +const builder = new ChainProviderBuilder(); + +// Resume from the state saved by the previous run, when there is one. +const saved = localStorage.getItem(`chain-db:${genesis}`); +if (saved) builder.setDatabase(genesis, saved); + +const provider = builder.build(); +const connection = await provider.connect(genesis); + +connection.send('{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_genesisHash","params":[]}'); +const response = await connection.nextResponse(); // undefined once closed +connection.close(); + +// Later, once the light client has synced, save state for the next launch. +localStorage.setItem(`chain-db:${genesis}`, await provider.snapshot(genesis)); +``` + +Warm start is driven by the host: call `snapshot()` once the light client has +synced, persist the string, and hand it back through `setDatabase()` before +`build()` on the next launch. + +## Native hosts + +Android and iOS do not consume this package. The same `truapi-provider` crate is +published for them as its own artifacts over UniFFI — a `TrUAPIProvider` Swift +package and a `truapi-provider-android` AAR — exposing the same `ChainProvider` +contract with the same bundled catalog, so the wiring differs only in language. + +## Building + +The `dist/` bundle is generated and gitignored. Rebuild it from the Rust crate: + +```bash +npm run build:wasm # wasm-pack --target web, features "js networks" +``` + +`wasm-pack` is required (`cargo install wasm-pack`). Set `TRUAPI_WASM_PROFILE=dev` +for a fast unoptimized build. The repo's `make wasm` target rebuilds this bundle +alongside the host runtime. + +## License + +MIT AND Apache-2.0. See [LICENSE](LICENSE), [LICENSE-APACHE](LICENSE-APACHE), and +[NOTICE](NOTICE). diff --git a/js/packages/truapi-provider/package.json b/js/packages/truapi-provider/package.json new file mode 100644 index 000000000..1dee2aff9 --- /dev/null +++ b/js/packages/truapi-provider/package.json @@ -0,0 +1,43 @@ +{ + "name": "@parity/truapi-provider", + "version": "0.1.0", + "description": "Network provider backends for the TrUAPI ChainProvider: an embedded smoldot light client and remote WebSocket JSON-RPC nodes, compiled to WebAssembly for browser hosts", + "license": "MIT AND Apache-2.0", + "author": "Parity Technologies ", + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/truapi.git", + "directory": "js/packages/truapi-provider" + }, + "homepage": "https://github.com/paritytech/truapi#readme", + "bugs": { + "url": "https://github.com/paritytech/truapi/issues" + }, + "type": "module", + "main": "dist/truapi_provider.js", + "types": "dist/truapi_provider.d.ts", + "sideEffects": [ + "./dist/truapi_provider.js" + ], + "exports": { + ".": { + "types": "./dist/truapi_provider.d.ts", + "import": "./dist/truapi_provider.js" + }, + "./truapi_provider_bg.wasm": "./dist/truapi_provider_bg.wasm" + }, + "files": [ + "dist", + "README.md", + "LICENSE", + "LICENSE-APACHE", + "NOTICE" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "node scripts/build-wasm.mjs", + "build:wasm": "node scripts/build-wasm.mjs" + } +} diff --git a/js/packages/truapi-provider/scripts/build-wasm.mjs b/js/packages/truapi-provider/scripts/build-wasm.mjs new file mode 100644 index 000000000..a707372bf --- /dev/null +++ b/js/packages/truapi-provider/scripts/build-wasm.mjs @@ -0,0 +1,80 @@ +#!/usr/bin/env node +// Rebuild the browser `@parity/truapi-provider` WASM bundle under `dist/` from +// the `truapi-provider` Rust crate. wasm-pack is required. +// +// The `js` feature exposes the wasm-bindgen provider API and `networks` bundles +// the smoldot light client plus the chain-spec catalog, so a consumer can +// `connect(genesisHash)` against a bundled network without shipping its own +// specs. + +import { execFile } from "node:child_process"; +import { readFile, rm } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; + +const execFileAsync = promisify(execFile); +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pkgRoot = resolve(__dirname, ".."); +const repoRoot = resolve(pkgRoot, "../../.."); +const rustCrate = resolve(repoRoot, "rust/crates/truapi-provider"); +const outDir = resolve(pkgRoot, "dist"); +const wasmProfile = process.env.TRUAPI_WASM_PROFILE ?? "release"; + +function args() { + const command = [ + "build", + "--target", + "web", + "--out-dir", + outDir, + "--out-name", + "truapi_provider", + ]; + if (wasmProfile === "dev") { + command.push("--dev"); + } else if (wasmProfile === "profiling") { + command.push("--profiling"); + } else if (wasmProfile !== "release") { + throw new Error( + `Unsupported TRUAPI_WASM_PROFILE=${wasmProfile}; expected release, dev, or profiling`, + ); + } + command.push(rustCrate, "--features", "js networks"); + return command; +} + +function formatBytes(bytes) { + if (bytes < 1024) return `${bytes} B`; + const kib = bytes / 1024; + return kib < 1024 ? `${kib.toFixed(1)} KiB` : `${(kib / 1024).toFixed(2)} MiB`; +} + +process.stdout.write( + `wasm-pack build --target web --${wasmProfile} → ${outDir}\n`, +); +try { + await execFileAsync("wasm-pack", args(), { cwd: repoRoot }); +} catch (err) { + if (err?.code === "ENOENT") { + console.error( + "wasm-pack is required. Install it with `cargo install wasm-pack` " + + "or see https://rustwasm.github.io/wasm-pack/installer/", + ); + process.exit(1); + } + throw err; +} + +// wasm-pack writes a nested `.gitignore: *`, its own minimal package.json, and +// copies the crate's license files into the bundle. This package's own +// package.json and top-level license files are authoritative, and the repo owns +// the ignore rules, so drop the generated copies. +await Promise.all([ + rm(resolve(outDir, ".gitignore"), { force: true }), + rm(resolve(outDir, "package.json"), { force: true }), + rm(resolve(outDir, "LICENSE-APACHE"), { force: true }), +]); + +const wasm = await readFile(resolve(outDir, "truapi_provider_bg.wasm")); +process.stdout.write(`wasm size: ${formatBytes(wasm.length)}\n`); diff --git a/package-lock.json b/package-lock.json index 70ca18fb0..99fa80e60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,11 @@ "typescript": "^5.7" } }, + "js/packages/truapi-provider": { + "name": "@parity/truapi-provider", + "version": "0.1.0", + "license": "MIT AND Apache-2.0" + }, "js/packages/truapi/node_modules/typescript": { "version": "6.0.3", "dev": true, @@ -431,6 +436,10 @@ "resolved": "js/packages/truapi-host", "link": true }, + "node_modules/@parity/truapi-provider": { + "resolved": "js/packages/truapi-provider", + "link": true + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", diff --git a/rust/crates/truapi-provider/Cargo.toml b/rust/crates/truapi-provider/Cargo.toml new file mode 100644 index 000000000..6cdd55352 --- /dev/null +++ b/rust/crates/truapi-provider/Cargo.toml @@ -0,0 +1,131 @@ +[package] +name = "truapi-provider" +version = "0.1.0" +edition.workspace = true +description = "Network provider backends for ChainProvider: remote WebSocket JSON-RPC nodes on every target, plus an embedded smoldot light client on native targets" +license = "MIT AND Apache-2.0" + +[lib] +# All three are load-bearing, one per artifact this crate ships as: `rlib` for +# the workspace, its tests and examples; `cdylib` for the wasm-bindgen bundle and +# the Android JNI library; `staticlib` for the iOS xcframework. Cargo has no way +# to select these per target, so every build emits all three. Only the linker +# step for the artifact being built actually consumes one. +crate-type = ["rlib", "cdylib", "staticlib"] + +# Builds the uniffi-bindgen binary used to generate the Swift bindings. +[[bin]] +name = "uniffi-bindgen" +required-features = ["cli"] + +# wasm-pack's bundled wasm-opt rejects the bulk-memory ops modern rustc emits +# for wasm32; skip it. The wasm-bindgen output is already valid for browsers, +# and bundlers (e.g. Vite) optimize the asset downstream. +[package.metadata.wasm-pack.profile.release] +wasm-opt = false + +[package.metadata.wasm-pack.profile.dev] +wasm-opt = false + +[features] +default = ["ws"] +ws = [ + "dep:jsonrpsee-client-transport", + "dep:jsonrpsee-core", + "dep:tokio", + "dep:js-sys", + "dep:send_wrapper", + "dep:wasm-bindgen", + "dep:web-sys", +] +smoldot = [ + "dep:futures-timer", + "dep:getrandom", + "dep:js-sys", + "dep:pin-project", + "dep:rand", + "dep:send_wrapper", + "dep:smoldot", + "dep:smoldot-light", + "dep:thiserror", + "dep:wasm-bindgen", + "dep:wasm-bindgen-futures", + "dep:web-sys", + "dep:web-time", +] +js = ["ws", "dep:wasm-bindgen-futures", "dep:tracing-subscriber"] +networks = ["smoldot"] +# Swift/UniFFI bindings for native hosts (iOS, macOS, ...). +uniffi = ["smoldot", "networks", "dep:uniffi"] +# Builds the uniffi-bindgen binary (implies uniffi). +cli = ["uniffi", "uniffi/cli"] + +[dependencies] +truapi = { path = "../truapi" } +truapi-platform = { path = "../truapi-platform" } +derive_more = { version = "2", features = ["display"] } +futures = "0.3" +hex = "0.4" +serde_json = "1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", default-features = false, features = ["registry", "std"], optional = true } +url = "2" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +jsonrpsee-client-transport = { version = "0.24", features = ["ws", "tls-rustls-platform-verifier"], optional = true } +jsonrpsee-core = { version = "0.24", features = ["client"], optional = true } +tokio = { version = "1", features = ["rt", "sync"], optional = true } +# Held at 1.3 even though 2.0 is published: subxt-lightclient 0.50.3, which truapi-server pulls in +# through subxt, requires smoldot-light ^1.3.1. Moving this crate to 2.0 on its own resolves to two +# light clients in one binary, which a wasm bundle cannot afford. Bump it when subxt requires 2.0. +smoldot-light = { version = "1.3", default-features = false, features = ["std"], optional = true } +rand = { version = "0.8", optional = true } +thiserror = { version = "1", optional = true } +uniffi = { workspace = true, optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +futures-timer = { version = "3", features = ["wasm-bindgen"], optional = true } +getrandom = { version = "0.2", features = ["js"], optional = true } +js-sys = { version = "0.3", optional = true } +pin-project = { version = "1", optional = true } +send_wrapper = { version = "0.6", optional = true } +smoldot = { version = "2", default-features = false, features = ["std"], optional = true } +# See the native target's note: pinned to 1.3 by subxt-lightclient, not by choice. +smoldot-light = { version = "1.3", default-features = false, optional = true } +thiserror = { version = "1", optional = true } +wasm-bindgen = { version = "0.2.118", optional = true } +wasm-bindgen-futures = { version = "0.4", optional = true } +web-sys = { version = "0.3", features = [ + "BinaryType", + "CloseEvent", + "console", + "Event", + "MessageEvent", + "WebSocket", +], optional = true } +web-time = { version = "1", optional = true } + +[dev-dependencies] +# Derives a bundled spec's genesis hash so the catalog cannot drift off it. +blake2b_simd = { version = "1", default-features = false } + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +jsonrpsee = { version = "0.24", features = ["server"] } +tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "time"] } +tokio-tungstenite = "0.24" + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-test = "0.3" + +[[example]] +name = "paseo_demo" +required-features = ["ws", "smoldot"] + +[[example]] +name = "gateway" +required-features = ["ws", "networks"] + +# `deny` rather than `forbid`: the `uniffi` feature scopes an `allow(unsafe_code)` +# for UniFFI's generated FFI glue (see src/lib.rs). Every other build has no unsafe. +[lints.rust] +unsafe_code = "deny" diff --git a/rust/crates/truapi-provider/LICENSE-APACHE b/rust/crates/truapi-provider/LICENSE-APACHE new file mode 100644 index 000000000..834d9d9c1 --- /dev/null +++ b/rust/crates/truapi-provider/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative + Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rust/crates/truapi-provider/NOTICE b/rust/crates/truapi-provider/NOTICE new file mode 100644 index 000000000..f4984a2da --- /dev/null +++ b/rust/crates/truapi-provider/NOTICE @@ -0,0 +1,19 @@ +truapi-provider + +This crate is licensed MIT (see the repository LICENSE) except for the files +under src/light_platform_web/, which are vendored from subxt-lightclient and +licensed under Apache-2.0 (see LICENSE-APACHE). + +Vendored components +------------------- + +src/light_platform_web/{platform,helpers,socket}.rs + Copyright 2019-2026 Parity Technologies (UK) Ltd. + Vendored from subxt-lightclient 0.50.1 (github.com/paritytech/subxt, + crates/lightclient), which is dual-licensed Apache-2.0 OR GPL-3.0; used here + under Apache-2.0. The `onmessage` handler in socket.rs was modified to ignore + non-ArrayBuffer frames instead of panicking; otherwise the copy is faithful. + +The embedded light client itself (the `smoldot` / `smoldot-light` crates, +GPL-3.0-or-later WITH Classpath-exception-2.0) is an ordinary Cargo dependency, +linked under the Classpath exception; its source is not vendored here. diff --git a/rust/crates/truapi-provider/build-ios-xcframework.sh b/rust/crates/truapi-provider/build-ios-xcframework.sh new file mode 100755 index 000000000..bfcf1458a --- /dev/null +++ b/rust/crates/truapi-provider/build-ios-xcframework.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Build the truapi-provider xcframework and Swift bindings for iOS from the +# crate's `uniffi` feature. +# +# Produces (under the workspace target/ dir): +# - target/TruapiProviderFFI.xcframework (static lib + module map) +# - target/ios-bindings/truapi_provider.swift +# +# Add the xcframework and the generated .swift to an Xcode target. By default +# only the simulator slice is built; pass `--device` to add the arm64 device +# slice too. +set -euo pipefail + +LIB=libtruapi_provider.a +PROFILE=${PROFILE:-debug} +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +cd "$ROOT" + +CARGO_FLAGS=(-p truapi-provider --lib --no-default-features --features uniffi) +[ "$PROFILE" = release ] && CARGO_FLAGS+=(--release) + +SLICES=(aarch64-apple-ios-sim) +[ "${1:-}" = "--device" ] && SLICES+=(aarch64-apple-ios) + +for target in "${SLICES[@]}"; do + rustup target add "$target" >/dev/null 2>&1 || true + echo "==> building $target ($PROFILE)" + cargo build "${CARGO_FLAGS[@]}" --target "$target" +done + +BINDINGS="target/ios-bindings" +rm -rf "$BINDINGS" && mkdir -p "$BINDINGS" +echo "==> generating Swift bindings" +cargo run -q -p truapi-provider --features cli --bin uniffi-bindgen -- \ + generate --library "target/aarch64-apple-ios-sim/$PROFILE/$LIB" \ + --language swift --out-dir "$BINDINGS" + +HEADERS="target/ios-headers" +rm -rf "$HEADERS" && mkdir -p "$HEADERS" +cp "$BINDINGS/truapi_providerFFI.h" "$HEADERS/" +cp "$BINDINGS/truapi_providerFFI.modulemap" "$HEADERS/module.modulemap" + +OUT="target/TruapiProviderFFI.xcframework" +rm -rf "$OUT" +ARGS=() +for target in "${SLICES[@]}"; do + ARGS+=(-library "target/$target/$PROFILE/$LIB" -headers "$HEADERS") +done +echo "==> packaging $OUT" +xcodebuild -create-xcframework "${ARGS[@]}" -output "$OUT" +echo "done: $OUT" diff --git a/rust/crates/truapi-provider/examples/gateway-dotli-paseo-next-v2.json b/rust/crates/truapi-provider/examples/gateway-dotli-paseo-next-v2.json new file mode 100644 index 000000000..5db66f670 --- /dev/null +++ b/rust/crates/truapi-provider/examples/gateway-dotli-paseo-next-v2.json @@ -0,0 +1,16 @@ +{ + "listen": "127.0.0.1:9944", + "chains": { + "relay": { + "genesis": "0x374057be67b355151f271ff70c3db98308c62c8adc48dc6724b6a009a1a014fd" + }, + "asset-hub": { + "genesis": "0xbf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f", + "url": "wss://paseo-asset-hub-next-rpc.polkadot.io" + }, + "people": { + "genesis": "0xc5af1826b31493f08b7e2a823842f98575b806a784126f28da9608c68665afa5", + "url": "wss://paseo-people-next-system-rpc.polkadot.io" + } + } +} diff --git a/rust/crates/truapi-provider/examples/gateway.rs b/rust/crates/truapi-provider/examples/gateway.rs new file mode 100644 index 000000000..a23c0e131 --- /dev/null +++ b/rust/crates/truapi-provider/examples/gateway.rs @@ -0,0 +1,200 @@ +//! Local WebSocket gateway exposing [`EmbeddedChainProvider`] chains to a +//! browser host: each configured chain is served at `ws://LISTEN/`, and +//! every inbound WebSocket connection becomes one provider connection. +//! +//! dotli's `rpc-gateway` backend can point at this process via its +//! `dotli:gateway-rpc-base` setting (e.g. `ws://127.0.0.1:9944`), which routes +//! the host's relay/asset-hub/people traffic here — light-client-verified +//! where a chain runs on the embedded smoldot, proxied where it targets a +//! remote node. +//! +//! Usage: +//! +//! ```text +//! cargo run -p truapi-provider --features networks --example gateway -- CONFIG.json +//! ``` +//! +//! Config shape: each chain is a light client resolved from the bundled network +//! catalog by its genesis hash (relay wiring included), or a proxy to a remote +//! node via `url`: +//! +//! ```json +//! { +//! "listen": "127.0.0.1:9944", +//! "chains": { +//! "relay": { "genesis": "0x…" }, +//! "asset-hub": { "genesis": "0x…", "url": "wss://node.example" } +//! } +//! } +//! ``` + +// The example is native-only; the wasm build gets a stub main so `cargo test +// --target wasm32-unknown-unknown` can still build every target. +#[cfg(target_arch = "wasm32")] +fn main() {} + +#[cfg(not(target_arch = "wasm32"))] +#[tokio::main] +async fn main() { + imp::run().await; +} + +#[cfg(not(target_arch = "wasm32"))] +mod imp { + use std::collections::HashMap; + use std::net::SocketAddr; + use std::sync::Arc; + + use futures::{SinkExt, StreamExt}; + use serde_json::Value; + use tokio::net::{TcpListener, TcpStream}; + use tokio_tungstenite::tungstenite::Message; + use tokio_tungstenite::tungstenite::handshake::server::{Request, Response}; + use truapi_platform::ChainProvider; + use truapi_provider::{ChainSource, EmbeddedChainProvider}; + + pub(super) async fn run() { + let config_path = std::env::args().nth(1).unwrap_or_else(|| usage()); + let config: Value = serde_json::from_str( + &std::fs::read_to_string(&config_path).expect("the config file must be readable"), + ) + .expect("the config file must be valid JSON"); + + let listen = config["listen"] + .as_str() + .unwrap_or("127.0.0.1:9944") + .to_owned(); + let chains = config["chains"] + .as_object() + .expect("config.chains must be an object"); + + let mut builder = EmbeddedChainProvider::builder(); + let mut routes = HashMap::new(); + for (name, entry) in chains { + let genesis = parse_genesis( + entry["genesis"] + .as_str() + .expect("chain.genesis is required"), + ); + // A `url` entry is proxied to a remote node; every other entry is a + // light client the catalog resolves from its genesis hash — relay + // wiring for parachains comes from the catalog, not this config. + if let Some(url) = entry["url"].as_str() { + println!("[gateway] /{name}: proxy to {url}"); + builder = builder.chain( + genesis, + ChainSource::rpc_node(url::Url::parse(url).expect("chain.url must parse")), + ); + } else { + println!("[gateway] /{name}: catalog light client"); + } + routes.insert(format!("/{name}"), genesis); + } + + let provider = Arc::new(builder.build()); + let routes = Arc::new(routes); + let listener = TcpListener::bind(&listen) + .await + .expect("the gateway must bind its listen address"); + println!("[gateway] listening on ws://{listen}"); + + loop { + let (stream, peer) = listener.accept().await.expect("accept must succeed"); + tokio::spawn(serve( + stream, + peer, + Arc::clone(&provider), + Arc::clone(&routes), + )); + } + } + + /// Bridge one WebSocket connection to one provider connection. + async fn serve( + stream: TcpStream, + peer: SocketAddr, + provider: Arc, + routes: Arc>, + ) { + let mut path = String::new(); + let websocket = match tokio_tungstenite::accept_hdr_async( + stream, + // The callback signature (and its large Err variant) is fixed by + // tungstenite's accept_hdr_async. + #[allow(clippy::result_large_err)] + |request: &Request, response: Response| { + path = request.uri().path().to_owned(); + Ok(response) + }, + ) + .await + { + Ok(websocket) => websocket, + Err(err) => { + eprintln!("[gateway] {peer}: handshake failed: {err}"); + return; + } + }; + + let Some(genesis) = routes.get(&path) else { + eprintln!("[gateway] {peer}: unknown route {path}"); + return; + }; + let connection = match provider.connect(*genesis).await { + Ok(connection) => connection, + Err(err) => { + eprintln!( + "[gateway] {peer}: connect for {path} failed: {}", + err.reason + ); + return; + } + }; + println!("[gateway] {peer}: connected to {path}"); + + let (mut outbound, mut inbound) = websocket.split(); + let mut responses = connection.responses(); + loop { + tokio::select! { + frame = inbound.next() => match frame { + Some(Ok(Message::Text(text))) => connection.send(text), + Some(Ok(Message::Binary(bytes))) => match String::from_utf8(bytes) { + Ok(text) => connection.send(text), + Err(_) => eprintln!("[gateway] {peer}: dropping non-UTF-8 frame"), + }, + Some(Ok(Message::Close(_))) | None => break, + // Ping/pong is answered by tungstenite itself. + Some(Ok(_)) => {} + Some(Err(err)) => { + eprintln!("[gateway] {peer}: receive failed: {err}"); + break; + } + }, + response = responses.next() => match response { + Some(text) => { + if outbound.send(Message::Text(text)).await.is_err() { + break; + } + } + None => break, + }, + } + } + + connection.close(); + let _ = outbound.close().await; + println!("[gateway] {peer}: disconnected from {path}"); + } + + fn usage() -> ! { + eprintln!("usage: gateway CONFIG.json"); + std::process::exit(2); + } + + fn parse_genesis(hex_str: &str) -> [u8; 32] { + hex::decode(hex_str.trim_start_matches("0x")) + .expect("genesis hashes are valid hex") + .try_into() + .expect("genesis hashes are 32 bytes") + } +} diff --git a/rust/crates/truapi-provider/examples/paseo_demo.rs b/rust/crates/truapi-provider/examples/paseo_demo.rs new file mode 100644 index 000000000..5c0b03549 --- /dev/null +++ b/rust/crates/truapi-provider/examples/paseo_demo.rs @@ -0,0 +1,255 @@ +//! Runs the same chainHead_v1 flow over both provider backends against the +//! Paseo relay chain: follow the chain, wait for `initialized`, read the +//! `Timestamp::Now` storage entry at the finalized block, and print it as a +//! wall-clock time. +//! +//! Usage: +//! +//! ```text +//! cargo run -p truapi-provider --features smoldot --example paseo_demo -- ws [WS_URL] +//! cargo run -p truapi-provider --features smoldot --example paseo_demo -- light CHAIN_SPEC_PATH +//! ``` +//! +//! A Paseo relay chain spec is available at +//! . +//! The light-client leg warp-syncs from scratch and can take tens of seconds. + +// The example is native-only; the wasm build gets a stub main so `cargo test +// --target wasm32-unknown-unknown` can still build every target. +#[cfg(target_arch = "wasm32")] +fn main() {} + +#[cfg(not(target_arch = "wasm32"))] +#[tokio::main] +async fn main() { + imp::run().await; +} + +#[cfg(not(target_arch = "wasm32"))] +mod imp { + use std::time::{Duration, Instant, UNIX_EPOCH}; + + use futures::stream::{BoxStream, StreamExt}; + use serde_json::{Value, json}; + use truapi_platform::ChainProvider; + use truapi_provider::{ChainSource, EmbeddedChainProvider}; + + /// Paseo relay-chain genesis hash. + const PASEO_GENESIS_HEX: &str = + "374057be67b355151f271ff70c3db98308c62c8adc48dc6724b6a009a1a014fd"; + + /// Public Paseo WebSocket endpoint used when none is given. + const DEFAULT_WS_URL: &str = "wss://paseo.rpc.amforc.com"; + + /// Storage key of `Timestamp::Now` (`twox128("Timestamp") ++ twox128("Now")`). + const TIMESTAMP_NOW_KEY: &str = + "0xf0c365c3cf59d671eb72da0e7a4113c49f1f0515f462cdcf84e0f1d6045dfcbb"; + + const STEP_TIMEOUT: Duration = Duration::from_secs(300); + + pub(super) async fn run() { + let mut args = std::env::args().skip(1); + let mode = args.next().unwrap_or_else(|| usage()); + let source = match mode.as_str() { + "ws" => { + let url = args.next().unwrap_or_else(|| DEFAULT_WS_URL.to_owned()); + let url = url::Url::parse(&url).expect("the WS URL must parse"); + ChainSource::rpc_node(url) + } + "light" => { + let path = args.next().unwrap_or_else(|| usage()); + let spec = std::fs::read_to_string(&path).expect("the chain spec must be readable"); + ChainSource::light_client(spec).build() + } + _ => usage(), + }; + + let genesis = parse_genesis(PASEO_GENESIS_HEX); + let provider = EmbeddedChainProvider::builder() + .chain(genesis, source) + .build(); + + let started = Instant::now(); + let connection = provider + .connect(genesis) + .await + .expect("connecting to Paseo must succeed"); + println!("[{mode}] connected in {:?}", started.elapsed()); + + let mut responses = connection.responses(); + + connection.send( + json!({ + "jsonrpc": "2.0", + "id": "genesis", + "method": "chainSpec_v1_genesisHash", + "params": [], + }) + .to_string(), + ); + let live_genesis = wait_for(&mut responses, |frame| { + (frame["id"] == "genesis").then(|| frame["result"].as_str().map(str::to_owned))? + }) + .await; + assert_eq!( + live_genesis.trim_start_matches("0x"), + PASEO_GENESIS_HEX, + "the endpoint serves a different chain than the Paseo genesis constant" + ); + println!("[{mode}] genesis hash verified: {live_genesis}"); + + connection.send( + json!({ + "jsonrpc": "2.0", + "id": "follow", + "method": "chainHead_v1_follow", + "params": [false], + }) + .to_string(), + ); + let follow_response = wait_for(&mut responses, |frame| { + (frame["id"] == "follow").then(|| frame["result"].as_str().map(str::to_owned))? + }) + .await; + println!("[{mode}] follow subscription: {follow_response}"); + + let initialized = wait_for(&mut responses, |frame| { + let event = follow_event(frame, &follow_response)?; + println!("[{mode}] follow event: {}", event["event"]); + (event["event"] == "initialized").then(|| event.clone()) + }) + .await; + let finalized_hash = initialized["finalizedBlockHashes"] + .as_array() + .and_then(|hashes| hashes.last()) + .and_then(Value::as_str) + .expect("initialized carries at least one finalized hash") + .to_owned(); + println!( + "[{mode}] initialized at {finalized_hash} after {:?}", + started.elapsed() + ); + + // Prefer a best block when one shows up quickly: on a chain with lagging + // finality, full nodes may have pruned the finalized block's state. + let best_hash = try_wait_for(&mut responses, Duration::from_secs(10), |frame| { + let event = follow_event(frame, &follow_response)?; + (event["event"] == "bestBlockChanged") + .then(|| event["bestBlockHash"].as_str().map(str::to_owned))? + }) + .await; + let target_hash = best_hash.unwrap_or_else(|| finalized_hash.clone()); + println!("[{mode}] reading storage at {target_hash}"); + + let read_started = Instant::now(); + connection.send( + json!({ + "jsonrpc": "2.0", + "id": "storage", + "method": "chainHead_v1_storage", + "params": [ + follow_response, + target_hash, + [{ "key": TIMESTAMP_NOW_KEY, "type": "value" }], + null, + ], + }) + .to_string(), + ); + let value = wait_for(&mut responses, |frame| { + let event = follow_event(frame, &follow_response)?; + match event["event"].as_str() { + Some("operationInaccessible") | Some("operationError") => { + panic!("the storage read failed: {event}") + } + Some("operationStorageItems") => event["items"] + .as_array() + .and_then(|items| items.first()) + .and_then(|item| item["value"].as_str()) + .map(str::to_owned), + _ => None, + } + }) + .await; + println!( + "[{mode}] Timestamp::Now = {value} (read took {:?})", + read_started.elapsed() + ); + + let millis = u64::from_le_bytes( + parse_hex(&value) + .try_into() + .expect("Timestamp::Now is a u64"), + ); + let timestamp = UNIX_EPOCH + Duration::from_millis(millis); + println!( + "[{mode}] on-chain time: {timestamp:?} (total {:?})", + started.elapsed() + ); + + connection.send( + json!({ + "jsonrpc": "2.0", + "id": "unfollow", + "method": "chainHead_v1_unfollow", + "params": [follow_response], + }) + .to_string(), + ); + connection.close(); + } + + fn usage() -> ! { + eprintln!("usage: paseo_demo ws [WS_URL] | paseo_demo light CHAIN_SPEC_PATH"); + std::process::exit(2); + } + + /// Extract the follow-event payload if `frame` is a `chainHead_v1_followEvent` + /// notification for `subscription`. + fn follow_event<'a>(frame: &'a Value, subscription: &str) -> Option<&'a Value> { + (frame["method"] == "chainHead_v1_followEvent" + && frame["params"]["subscription"] == subscription) + .then(|| &frame["params"]["result"]) + } + + /// Read frames until `pick` extracts a value; panics on stream end or timeout. + async fn wait_for( + responses: &mut BoxStream<'static, String>, + pick: impl FnMut(&Value) -> Option, + ) -> T { + try_wait_for(responses, STEP_TIMEOUT, pick) + .await + .expect("timed out waiting for a JSON-RPC frame") + } + + /// Read frames until `pick` extracts a value or `limit` elapses. + async fn try_wait_for( + responses: &mut BoxStream<'static, String>, + limit: Duration, + mut pick: impl FnMut(&Value) -> Option, + ) -> Option { + let deadline = Instant::now() + limit; + loop { + let remaining = deadline.checked_duration_since(Instant::now())?; + let frame = tokio::time::timeout(remaining, responses.next()) + .await + .ok()? + .expect("the connection ended unexpectedly"); + let frame: Value = serde_json::from_str(&frame).expect("frames are valid JSON"); + if let Some(value) = pick(&frame) { + return Some(value); + } + } + } + + fn parse_genesis(hex_str: &str) -> [u8; 32] { + hex::decode(hex_str) + .expect("the genesis constant is valid hex") + .try_into() + .expect("the genesis constant is 32 bytes") + } + + fn parse_hex(value: &str) -> Vec { + hex::decode(value.trim_start_matches("0x")).expect("storage values are valid hex") + } +} diff --git a/rust/crates/truapi-provider/networks/paseo-next-v2-asset-hub.json b/rust/crates/truapi-provider/networks/paseo-next-v2-asset-hub.json new file mode 100644 index 000000000..8bb1e8601 --- /dev/null +++ b/rust/crates/truapi-provider/networks/paseo-next-v2-asset-hub.json @@ -0,0 +1 @@ +{"name":"Paseo Asset Hub Next","id":"next-asset-hub-paseo","chainType":"Live","bootNodes":["/dns/paseo-asset-hub-next-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWKT5DcVLoBbVDAM6N5ujDVknPfQWHk8SGJqJPyAM8Z4Y4","/dns/paseo-asset-hub-next-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWFjpVdBnfJFntogWhinn15WW2n8Fd2DiAcqU6i9rg47Yg"],"telemetryEndpoints":null,"protocolId":null,"properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"relay_chain":"paseo","para_id":1500,"codeSubstitutes":{},"genesis":{"stateRootHash":"0x2b1f5dafccbaeb2e52c553a57003ef77bf35920d86c2c932920e4a60c7f68a50"}} \ No newline at end of file diff --git a/rust/crates/truapi-provider/networks/paseo-next-v2-bulletin.json b/rust/crates/truapi-provider/networks/paseo-next-v2-bulletin.json new file mode 100644 index 000000000..c0096ab19 --- /dev/null +++ b/rust/crates/truapi-provider/networks/paseo-next-v2-bulletin.json @@ -0,0 +1 @@ +{"name":"Paseo Bulletin Next","id":"next-bulletin-paseo","chainType":"Live","bootNodes":["/dns4/paseo-bulletin-next-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDGdPBWpytPdNAXDT2KJWwmPXkxvxyQLGc7pRdFWeZnyB","/dns4/paseo-bulletin-next-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWC45NgktSLMPQafAhi8TMAtiiatnmNc3Qv6wA74u7YBVc","/dns4/paseo-bulletin-next-rpc-node-0.polkadot.io/tcp/443/wss/p2p/12D3KooWS4ptBbHGritdb1T7JPxKT2EN7FXvqq9rUp12jUvjnqQ1","/dns4/paseo-bulletin-next-rpc-node-1.polkadot.io/tcp/443/wss/p2p/12D3KooWKMc4jJsU7fdEsis4AsM8Assk5jFqhEUEa2ZSiWJGKpfv"],"telemetryEndpoints":null,"protocolId":null,"properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"relay_chain":"paseo","para_id":1501,"codeSubstitutes":{},"genesis":{"stateRootHash":"0xec731d2141c9f893c8467d28034b1006ace0336c708051516b59d6b628d10190"}} \ No newline at end of file diff --git a/rust/crates/truapi-provider/networks/paseo-next-v2-people.json b/rust/crates/truapi-provider/networks/paseo-next-v2-people.json new file mode 100644 index 000000000..731d0aaea --- /dev/null +++ b/rust/crates/truapi-provider/networks/paseo-next-v2-people.json @@ -0,0 +1 @@ +{"name":"Paseo People Next","id":"next-people-paseo","chainType":"Live","bootNodes":["/dns/paseo-people-next-system-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWN5JKhxNAfij5EvbY2Kdr5oJvAVhL5WovmxaZ2Q6m69uP","/dns/paseo-people-next-system-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWGboxsnnd1uqRUZUf3vL9fFgqnDvXxHtQ7LQN3YmPXhhY"],"telemetryEndpoints":null,"protocolId":null,"properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"relay_chain":"paseo","para_id":1502,"codeSubstitutes":{},"genesis":{"stateRootHash":"0x77fe37f8298f1f64025e6dcd5383a470f527cabae0a423e4379f471bb4c6819b"}} \ No newline at end of file diff --git a/rust/crates/truapi-provider/networks/paseo.json b/rust/crates/truapi-provider/networks/paseo.json new file mode 100644 index 000000000..23fcff34c --- /dev/null +++ b/rust/crates/truapi-provider/networks/paseo.json @@ -0,0 +1 @@ +{"name":"Paseo","id":"paseo","chainType":"Live","bootNodes":["/dns/n8r2.bn.turboflakes.io/tcp/30443/wss/p2p/12D3KooWC3noUwG6TJwh4AJMNxCMNvmSZeknsLtXudxrB7nPXbjG","/ip4/139.99.130.67/tcp/30333/p2p/12D3KooWCj2oX6oAV3NohsZbyPhNN3LAK9BcdBENTnbJWoFCX7jq"],"telemetryEndpoints":null,"protocolId":"pad","properties":{"ss58Format":"42","tokenDecimals":"10","tokenSymbol":"PAS"},"codeSubstitutes":{},"genesis":{"stateRootHash":"0x070b57c91484ab48bfa87a8af91ec6c20667aa4b0e7b34e3c0689dd00e7980ca"},"lightSyncState":{"babeEpochChanges":"0x04a0b428bc1f59a88957c980c72ebd4b9c8bccbe10ddd02375e838fb311b47cbb016b50800011e30bf11000000007632bf1100000000043517d05738281c24a35525f40bc2833a4014c77403ad4f01b14af1f0044280036eb70800017632bf1100000000ce34bf11000000000814c39a23dbf999353616a1948a54cd9e4559a9e091958b7dfcfbea806aa99883c6b9080001ce34bf11000000002637bf110000000000bd323d869d272911e57993568f44511581c5b0c3389e143f02f83086ac06d573c6b9080001ce34bf11000000002637bf110000000000001014c39a23dbf999353616a1948a54cd9e4559a9e091958b7dfcfbea806aa99883c6b9080001bd03000000000000ce34bf11000000005802000000000000e0e81d86891b29ffe5f8bf39108b666142cec991fb63244415b7ccea71125faf170100000000000000ec1bd0f5f84d72466a76e6a4475c503ab408f2b49edd66484c66481ea636b153010000000000000002d0a5c4dc88271d54931c74fca673f7574b0cca137085e7bd93d7c9a22c77590100000000000000a4ff948f85eef63658b3dae21f50cefa2c19cb89b0305fb53dfcea2f4491ab1c0100000000000000d6cb56c057956c71f0eb5675ec38553b81ca149a10130dedd485cb221f4f962e01000000000000008afead280e1c83ad0a461a6adbcf4e8328ff93d5f80d95d427d07fbe8d28195a010000000000000042137c0400236ce29118fc984cebdf78e1c5031a1bf660aa7d6a18cedaf40e3e01000000000000007a412f2591679f757469c1cce3cbb489b58a65c88bec143580a6b08467c0d66f0100000000000000e45f47896b345c2ad29a023b420970357490bcf8e5c80191ca7127bfb4f274530100000000000000f06a25a0b965df7b9c2f3102775d3b15d47ee65e7b4e2cf573a4c201c0220937010000000000000010c13aace80c749cd5537d4ba45e073907f4b7ccbbccd4ce58230fa9bc61b1010100000000000000723d1d0f8b624987acd5035935bc7874a5b03b59fc747a6962654e1c9b1aea41010000000000000006b94575d2b3f000398ed0c27ec1c948928a61899691915473e2c53251bffc61010000000000000072bc3ba78c0f00838af10c9547081397b45fb7c4ec177bac233b801d0e01aa6801000000000000006ea0348a5f164dabacd4ff64506b7413b3bc77b059ee1e02a058d2a55bcfa87b0100000000000000584ec0db2bae4f2340d44a3b69d300553dd1df298585ac76b9a1c68f5068976501000000000000006403b69b73dd4d134d158731eeab4f482cb8a67bb2710b9ef7ee346358abba01010000000000000096361f9708fc507af16090e372e889ac7926b4807e985ef5ae14b6a5d7396d3a0100000000000000de7500c97090be7a78c1b385f298de75c816a4281e91cfe80caee01b1ca0690d010000000000000028ef1ad31acaf344d82ab008882453399a7331a62fa1e32bff49d76105d8527c01000000000000004253bd1f6e0438b5856d2a0536511d345fe933b049392ea88043d142803f905701000000000000000efa3a3d1a1df84af870be04c2b873bc69ccf75d0a681ebee038ce0dd7ce3262010000000000000026026b3279655b5fc55c7a4cd07d3ec34a9674b71e6dd9cae3a382e625ba5534010000000000000006a88e1633465db194e7fc64c7a5c4a4b911ff24562b38a9e1fb36056514b63e0100000000000000365dce13c901ec12ae32f9eafbc7f0efd2ed0585ca7da7d44a3860f85e649e4e0100000000000000a0e0eacbb23ed6626698328118d5cf817ffff74ff0095e3a0283ce9d6dd908460100000000000000680ee03d3bfa8d45b30cc10f00bc089468b6744312542233ce457f4514adc831010000000000000048740433d5c90fbc833f743905d3ffa8c944a1c497c76303a934eb717bc1c76c01000000000000008819d545b5476790fd97a58a4f705208ac52c57cccee2237fbf37579fa4ea5470100000000000000606e0e33cb3f9fd7970a16a7d3f5b650c796f7d7c805c35c645c55d5ce6039020100000000000000d669b2ec823a24d60cfa2155190dbbd19168054643d870433b8aecaf538d99010100000000000000d800a130cf1abec4ab5e264e2910aff610830559c15c416e2e7293205c7df1680100000000000000d6d3cbdec3326586e224170d91fd0238fde441a902d80787f080d134fc20a52d01000000000000003047ebca8614f20e45cf0043eb1ec9089f10c6981ec49a909c4e7bea9614b20c010000000000000014d21668a49bece60e5c54352fae48b15f7c20c80d6ed87a05587e5bce12b16a010000000000000060e57bc9a25786e3001696943f4141c79fcdfcfdba9dd10f713c921c7383d72f01000000000000006816b19e22c57012997df7cbb387e8a043c5980ef6a0b7176ef7c9ef687692670100000000000000468da131acf1f73b47dfde211bcc53d2fc102c741d72c21b56b28fdabbc6bc440100000000000000c4603fa09b096832ff1d834be056cfdeeb922262cfc89e5e0cf3600b67dbbc1e0100000000000000dcda83b436c74ec22ff1ca033ca783d31c7973d9d8bb669795afee7d6aabb4350100000000000000485f4d8cf2897518552ab984b5074d823e3087289d03d776f7e8e245e23b493e0100000000000000205a7ff855c2cadd27748b723cef01126e9f522725ba798fbcd8e7426321b4570100000000000000dc0c4f99eaf6d3437e95af15d077d591a551d76ddd1579fb100c05fee3591d7c010000000000000088d964219dd6ebd538e1804f1ca69936bc65c21cfa963ac370e4a6ac3f3fbd61010000000000000006b47b04fdf9f25e7dc43645b294f210e8f32dfb4b3a7f91ad15ffcc4f0637550100000000000000bafd009a59b87c879e7f538be8c07b49076aa775171bad62ee4c66fca5e3ae5a0100000000000000c8fc89765a2029dbd5de48bd60e759d3fe2754c81082683b86d89eaffbcf485b010000000000000018133feb9a23fceff68551c4e7c0b706742bc14464f721db0b270579fd5c001d0100000000000000de24f7b0bd7e3e89a817ad1953f778cbfd8343482cf5e777ef8cea61517411750100000000000000e421f4a9c7afe912397156c58837488b31baef4d297ece4d04a20056f6922420010000000000000056daa624ab5e2023ede243ff830235a4cf0bf25b8be88f011ea0fef46910865801000000000000007c2b2650d170087313955372306682dbcf2d95a2ca923a77e6b646a9a10bf83f01000000000000007a694fc030469cf01585fd29ff37905fbfc55329288d2fdd5d5294bc52e3bd2901000000000000009a78cffb3684ca2fd4118c4f885315179ecdbe2748d29066802c2e4529255b140100000000000000be9379477d3cce786682133454af701de1c04fa52cdcc5e9cf23ce47399fd8620100000000000000582545d042f1891e4c643dfa2c060cb6111072d88a0a64b45836e026ccf06e2d0100000000000000709f540a13dd7e28a612ad2df786d4c621ac6b748a768471f38c4a73d296657401000000000000000400000000000000023517d05738281c24a35525f40bc2833a4014c77403ad4f01b14af1f0044280036eb7080001bc030000000000007632bf11000000005802000000000000e0e81d86891b29ffe5f8bf39108b666142cec991fb63244415b7ccea71125faf170100000000000000ec1bd0f5f84d72466a76e6a4475c503ab408f2b49edd66484c66481ea636b153010000000000000002d0a5c4dc88271d54931c74fca673f7574b0cca137085e7bd93d7c9a22c77590100000000000000a4ff948f85eef63658b3dae21f50cefa2c19cb89b0305fb53dfcea2f4491ab1c0100000000000000d6cb56c057956c71f0eb5675ec38553b81ca149a10130dedd485cb221f4f962e01000000000000008afead280e1c83ad0a461a6adbcf4e8328ff93d5f80d95d427d07fbe8d28195a010000000000000042137c0400236ce29118fc984cebdf78e1c5031a1bf660aa7d6a18cedaf40e3e01000000000000007a412f2591679f757469c1cce3cbb489b58a65c88bec143580a6b08467c0d66f0100000000000000e45f47896b345c2ad29a023b420970357490bcf8e5c80191ca7127bfb4f274530100000000000000f06a25a0b965df7b9c2f3102775d3b15d47ee65e7b4e2cf573a4c201c0220937010000000000000010c13aace80c749cd5537d4ba45e073907f4b7ccbbccd4ce58230fa9bc61b1010100000000000000723d1d0f8b624987acd5035935bc7874a5b03b59fc747a6962654e1c9b1aea41010000000000000006b94575d2b3f000398ed0c27ec1c948928a61899691915473e2c53251bffc61010000000000000072bc3ba78c0f00838af10c9547081397b45fb7c4ec177bac233b801d0e01aa6801000000000000006ea0348a5f164dabacd4ff64506b7413b3bc77b059ee1e02a058d2a55bcfa87b0100000000000000584ec0db2bae4f2340d44a3b69d300553dd1df298585ac76b9a1c68f5068976501000000000000006403b69b73dd4d134d158731eeab4f482cb8a67bb2710b9ef7ee346358abba01010000000000000096361f9708fc507af16090e372e889ac7926b4807e985ef5ae14b6a5d7396d3a0100000000000000de7500c97090be7a78c1b385f298de75c816a4281e91cfe80caee01b1ca0690d010000000000000028ef1ad31acaf344d82ab008882453399a7331a62fa1e32bff49d76105d8527c01000000000000004253bd1f6e0438b5856d2a0536511d345fe933b049392ea88043d142803f905701000000000000000efa3a3d1a1df84af870be04c2b873bc69ccf75d0a681ebee038ce0dd7ce3262010000000000000026026b3279655b5fc55c7a4cd07d3ec34a9674b71e6dd9cae3a382e625ba5534010000000000000006a88e1633465db194e7fc64c7a5c4a4b911ff24562b38a9e1fb36056514b63e0100000000000000365dce13c901ec12ae32f9eafbc7f0efd2ed0585ca7da7d44a3860f85e649e4e0100000000000000a0e0eacbb23ed6626698328118d5cf817ffff74ff0095e3a0283ce9d6dd908460100000000000000680ee03d3bfa8d45b30cc10f00bc089468b6744312542233ce457f4514adc831010000000000000048740433d5c90fbc833f743905d3ffa8c944a1c497c76303a934eb717bc1c76c01000000000000008819d545b5476790fd97a58a4f705208ac52c57cccee2237fbf37579fa4ea5470100000000000000606e0e33cb3f9fd7970a16a7d3f5b650c796f7d7c805c35c645c55d5ce6039020100000000000000d669b2ec823a24d60cfa2155190dbbd19168054643d870433b8aecaf538d99010100000000000000d800a130cf1abec4ab5e264e2910aff610830559c15c416e2e7293205c7df1680100000000000000d6d3cbdec3326586e224170d91fd0238fde441a902d80787f080d134fc20a52d01000000000000003047ebca8614f20e45cf0043eb1ec9089f10c6981ec49a909c4e7bea9614b20c010000000000000014d21668a49bece60e5c54352fae48b15f7c20c80d6ed87a05587e5bce12b16a010000000000000060e57bc9a25786e3001696943f4141c79fcdfcfdba9dd10f713c921c7383d72f01000000000000006816b19e22c57012997df7cbb387e8a043c5980ef6a0b7176ef7c9ef687692670100000000000000468da131acf1f73b47dfde211bcc53d2fc102c741d72c21b56b28fdabbc6bc440100000000000000c4603fa09b096832ff1d834be056cfdeeb922262cfc89e5e0cf3600b67dbbc1e0100000000000000dcda83b436c74ec22ff1ca033ca783d31c7973d9d8bb669795afee7d6aabb4350100000000000000485f4d8cf2897518552ab984b5074d823e3087289d03d776f7e8e245e23b493e0100000000000000205a7ff855c2cadd27748b723cef01126e9f522725ba798fbcd8e7426321b4570100000000000000dc0c4f99eaf6d3437e95af15d077d591a551d76ddd1579fb100c05fee3591d7c010000000000000088d964219dd6ebd538e1804f1ca69936bc65c21cfa963ac370e4a6ac3f3fbd61010000000000000006b47b04fdf9f25e7dc43645b294f210e8f32dfb4b3a7f91ad15ffcc4f0637550100000000000000bafd009a59b87c879e7f538be8c07b49076aa775171bad62ee4c66fca5e3ae5a0100000000000000c8fc89765a2029dbd5de48bd60e759d3fe2754c81082683b86d89eaffbcf485b010000000000000018133feb9a23fceff68551c4e7c0b706742bc14464f721db0b270579fd5c001d0100000000000000de24f7b0bd7e3e89a817ad1953f778cbfd8343482cf5e777ef8cea61517411750100000000000000e421f4a9c7afe912397156c58837488b31baef4d297ece4d04a20056f6922420010000000000000056daa624ab5e2023ede243ff830235a4cf0bf25b8be88f011ea0fef46910865801000000000000007c2b2650d170087313955372306682dbcf2d95a2ca923a77e6b646a9a10bf83f01000000000000007a694fc030469cf01585fd29ff37905fbfc55329288d2fdd5d5294bc52e3bd2901000000000000009a78cffb3684ca2fd4118c4f885315179ecdbe2748d29066802c2e4529255b140100000000000000be9379477d3cce786682133454af701de1c04fa52cdcc5e9cf23ce47399fd8620100000000000000582545d042f1891e4c643dfa2c060cb6111072d88a0a64b45836e026ccf06e2d0100000000000000057799c2bca229e3ad0a862a0b83f392e2884e9aac68c10fdcc10bc121d97b910100000000000000040000000000000002a0b428bc1f59a88957c980c72ebd4b9c8bccbe10ddd02375e838fb311b47cbb016b5080001bb030000000000001e30bf11000000005802000000000000e0e81d86891b29ffe5f8bf39108b666142cec991fb63244415b7ccea71125faf170100000000000000ec1bd0f5f84d72466a76e6a4475c503ab408f2b49edd66484c66481ea636b153010000000000000002d0a5c4dc88271d54931c74fca673f7574b0cca137085e7bd93d7c9a22c77590100000000000000a4ff948f85eef63658b3dae21f50cefa2c19cb89b0305fb53dfcea2f4491ab1c0100000000000000d6cb56c057956c71f0eb5675ec38553b81ca149a10130dedd485cb221f4f962e01000000000000008afead280e1c83ad0a461a6adbcf4e8328ff93d5f80d95d427d07fbe8d28195a010000000000000042137c0400236ce29118fc984cebdf78e1c5031a1bf660aa7d6a18cedaf40e3e01000000000000007a412f2591679f757469c1cce3cbb489b58a65c88bec143580a6b08467c0d66f0100000000000000e45f47896b345c2ad29a023b420970357490bcf8e5c80191ca7127bfb4f274530100000000000000f06a25a0b965df7b9c2f3102775d3b15d47ee65e7b4e2cf573a4c201c0220937010000000000000010c13aace80c749cd5537d4ba45e073907f4b7ccbbccd4ce58230fa9bc61b1010100000000000000723d1d0f8b624987acd5035935bc7874a5b03b59fc747a6962654e1c9b1aea41010000000000000006b94575d2b3f000398ed0c27ec1c948928a61899691915473e2c53251bffc61010000000000000072bc3ba78c0f00838af10c9547081397b45fb7c4ec177bac233b801d0e01aa6801000000000000006ea0348a5f164dabacd4ff64506b7413b3bc77b059ee1e02a058d2a55bcfa87b0100000000000000584ec0db2bae4f2340d44a3b69d300553dd1df298585ac76b9a1c68f5068976501000000000000006403b69b73dd4d134d158731eeab4f482cb8a67bb2710b9ef7ee346358abba01010000000000000096361f9708fc507af16090e372e889ac7926b4807e985ef5ae14b6a5d7396d3a0100000000000000de7500c97090be7a78c1b385f298de75c816a4281e91cfe80caee01b1ca0690d010000000000000028ef1ad31acaf344d82ab008882453399a7331a62fa1e32bff49d76105d8527c01000000000000004253bd1f6e0438b5856d2a0536511d345fe933b049392ea88043d142803f905701000000000000000efa3a3d1a1df84af870be04c2b873bc69ccf75d0a681ebee038ce0dd7ce3262010000000000000026026b3279655b5fc55c7a4cd07d3ec34a9674b71e6dd9cae3a382e625ba5534010000000000000006a88e1633465db194e7fc64c7a5c4a4b911ff24562b38a9e1fb36056514b63e0100000000000000365dce13c901ec12ae32f9eafbc7f0efd2ed0585ca7da7d44a3860f85e649e4e0100000000000000a0e0eacbb23ed6626698328118d5cf817ffff74ff0095e3a0283ce9d6dd908460100000000000000680ee03d3bfa8d45b30cc10f00bc089468b6744312542233ce457f4514adc831010000000000000048740433d5c90fbc833f743905d3ffa8c944a1c497c76303a934eb717bc1c76c01000000000000008819d545b5476790fd97a58a4f705208ac52c57cccee2237fbf37579fa4ea5470100000000000000606e0e33cb3f9fd7970a16a7d3f5b650c796f7d7c805c35c645c55d5ce6039020100000000000000d669b2ec823a24d60cfa2155190dbbd19168054643d870433b8aecaf538d99010100000000000000d800a130cf1abec4ab5e264e2910aff610830559c15c416e2e7293205c7df1680100000000000000d6d3cbdec3326586e224170d91fd0238fde441a902d80787f080d134fc20a52d01000000000000003047ebca8614f20e45cf0043eb1ec9089f10c6981ec49a909c4e7bea9614b20c010000000000000014d21668a49bece60e5c54352fae48b15f7c20c80d6ed87a05587e5bce12b16a010000000000000060e57bc9a25786e3001696943f4141c79fcdfcfdba9dd10f713c921c7383d72f01000000000000006816b19e22c57012997df7cbb387e8a043c5980ef6a0b7176ef7c9ef687692670100000000000000468da131acf1f73b47dfde211bcc53d2fc102c741d72c21b56b28fdabbc6bc440100000000000000c4603fa09b096832ff1d834be056cfdeeb922262cfc89e5e0cf3600b67dbbc1e0100000000000000dcda83b436c74ec22ff1ca033ca783d31c7973d9d8bb669795afee7d6aabb4350100000000000000485f4d8cf2897518552ab984b5074d823e3087289d03d776f7e8e245e23b493e0100000000000000205a7ff855c2cadd27748b723cef01126e9f522725ba798fbcd8e7426321b4570100000000000000dc0c4f99eaf6d3437e95af15d077d591a551d76ddd1579fb100c05fee3591d7c010000000000000088d964219dd6ebd538e1804f1ca69936bc65c21cfa963ac370e4a6ac3f3fbd61010000000000000006b47b04fdf9f25e7dc43645b294f210e8f32dfb4b3a7f91ad15ffcc4f0637550100000000000000bafd009a59b87c879e7f538be8c07b49076aa775171bad62ee4c66fca5e3ae5a0100000000000000c8fc89765a2029dbd5de48bd60e759d3fe2754c81082683b86d89eaffbcf485b010000000000000018133feb9a23fceff68551c4e7c0b706742bc14464f721db0b270579fd5c001d0100000000000000de24f7b0bd7e3e89a817ad1953f778cbfd8343482cf5e777ef8cea61517411750100000000000000e421f4a9c7afe912397156c58837488b31baef4d297ece4d04a20056f6922420010000000000000056daa624ab5e2023ede243ff830235a4cf0bf25b8be88f011ea0fef46910865801000000000000007c2b2650d170087313955372306682dbcf2d95a2ca923a77e6b646a9a10bf83f01000000000000007a694fc030469cf01585fd29ff37905fbfc55329288d2fdd5d5294bc52e3bd2901000000000000009a78cffb3684ca2fd4118c4f885315179ecdbe2748d29066802c2e4529255b140100000000000000be9379477d3cce786682133454af701de1c04fa52cdcc5e9cf23ce47399fd8620100000000000000582545d042f1891e4c643dfa2c060cb6111072d88a0a64b45836e026ccf06e2d0100000000000000383894e182152d9b64fb594edac596832ad1cdb8e076616f3b4c96e62ef0fa220100000000000000040000000000000002bd323d869d272911e57993568f44511581c5b0c3389e143f02f83086ac06d573c6b9080001bd03000000000000ce34bf11000000005802000000000000e0e81d86891b29ffe5f8bf39108b666142cec991fb63244415b7ccea71125faf170100000000000000ec1bd0f5f84d72466a76e6a4475c503ab408f2b49edd66484c66481ea636b153010000000000000002d0a5c4dc88271d54931c74fca673f7574b0cca137085e7bd93d7c9a22c77590100000000000000a4ff948f85eef63658b3dae21f50cefa2c19cb89b0305fb53dfcea2f4491ab1c0100000000000000d6cb56c057956c71f0eb5675ec38553b81ca149a10130dedd485cb221f4f962e01000000000000008afead280e1c83ad0a461a6adbcf4e8328ff93d5f80d95d427d07fbe8d28195a010000000000000042137c0400236ce29118fc984cebdf78e1c5031a1bf660aa7d6a18cedaf40e3e01000000000000007a412f2591679f757469c1cce3cbb489b58a65c88bec143580a6b08467c0d66f0100000000000000e45f47896b345c2ad29a023b420970357490bcf8e5c80191ca7127bfb4f274530100000000000000f06a25a0b965df7b9c2f3102775d3b15d47ee65e7b4e2cf573a4c201c0220937010000000000000010c13aace80c749cd5537d4ba45e073907f4b7ccbbccd4ce58230fa9bc61b1010100000000000000723d1d0f8b624987acd5035935bc7874a5b03b59fc747a6962654e1c9b1aea41010000000000000006b94575d2b3f000398ed0c27ec1c948928a61899691915473e2c53251bffc61010000000000000072bc3ba78c0f00838af10c9547081397b45fb7c4ec177bac233b801d0e01aa6801000000000000006ea0348a5f164dabacd4ff64506b7413b3bc77b059ee1e02a058d2a55bcfa87b0100000000000000584ec0db2bae4f2340d44a3b69d300553dd1df298585ac76b9a1c68f5068976501000000000000006403b69b73dd4d134d158731eeab4f482cb8a67bb2710b9ef7ee346358abba01010000000000000096361f9708fc507af16090e372e889ac7926b4807e985ef5ae14b6a5d7396d3a0100000000000000de7500c97090be7a78c1b385f298de75c816a4281e91cfe80caee01b1ca0690d010000000000000028ef1ad31acaf344d82ab008882453399a7331a62fa1e32bff49d76105d8527c01000000000000004253bd1f6e0438b5856d2a0536511d345fe933b049392ea88043d142803f905701000000000000000efa3a3d1a1df84af870be04c2b873bc69ccf75d0a681ebee038ce0dd7ce3262010000000000000026026b3279655b5fc55c7a4cd07d3ec34a9674b71e6dd9cae3a382e625ba5534010000000000000006a88e1633465db194e7fc64c7a5c4a4b911ff24562b38a9e1fb36056514b63e0100000000000000365dce13c901ec12ae32f9eafbc7f0efd2ed0585ca7da7d44a3860f85e649e4e0100000000000000a0e0eacbb23ed6626698328118d5cf817ffff74ff0095e3a0283ce9d6dd908460100000000000000680ee03d3bfa8d45b30cc10f00bc089468b6744312542233ce457f4514adc831010000000000000048740433d5c90fbc833f743905d3ffa8c944a1c497c76303a934eb717bc1c76c01000000000000008819d545b5476790fd97a58a4f705208ac52c57cccee2237fbf37579fa4ea5470100000000000000606e0e33cb3f9fd7970a16a7d3f5b650c796f7d7c805c35c645c55d5ce6039020100000000000000d669b2ec823a24d60cfa2155190dbbd19168054643d870433b8aecaf538d99010100000000000000d800a130cf1abec4ab5e264e2910aff610830559c15c416e2e7293205c7df1680100000000000000d6d3cbdec3326586e224170d91fd0238fde441a902d80787f080d134fc20a52d01000000000000003047ebca8614f20e45cf0043eb1ec9089f10c6981ec49a909c4e7bea9614b20c010000000000000014d21668a49bece60e5c54352fae48b15f7c20c80d6ed87a05587e5bce12b16a010000000000000060e57bc9a25786e3001696943f4141c79fcdfcfdba9dd10f713c921c7383d72f01000000000000006816b19e22c57012997df7cbb387e8a043c5980ef6a0b7176ef7c9ef687692670100000000000000468da131acf1f73b47dfde211bcc53d2fc102c741d72c21b56b28fdabbc6bc440100000000000000c4603fa09b096832ff1d834be056cfdeeb922262cfc89e5e0cf3600b67dbbc1e0100000000000000dcda83b436c74ec22ff1ca033ca783d31c7973d9d8bb669795afee7d6aabb4350100000000000000485f4d8cf2897518552ab984b5074d823e3087289d03d776f7e8e245e23b493e0100000000000000205a7ff855c2cadd27748b723cef01126e9f522725ba798fbcd8e7426321b4570100000000000000dc0c4f99eaf6d3437e95af15d077d591a551d76ddd1579fb100c05fee3591d7c010000000000000088d964219dd6ebd538e1804f1ca69936bc65c21cfa963ac370e4a6ac3f3fbd61010000000000000006b47b04fdf9f25e7dc43645b294f210e8f32dfb4b3a7f91ad15ffcc4f0637550100000000000000bafd009a59b87c879e7f538be8c07b49076aa775171bad62ee4c66fca5e3ae5a0100000000000000c8fc89765a2029dbd5de48bd60e759d3fe2754c81082683b86d89eaffbcf485b010000000000000018133feb9a23fceff68551c4e7c0b706742bc14464f721db0b270579fd5c001d0100000000000000de24f7b0bd7e3e89a817ad1953f778cbfd8343482cf5e777ef8cea61517411750100000000000000e421f4a9c7afe912397156c58837488b31baef4d297ece4d04a20056f6922420010000000000000056daa624ab5e2023ede243ff830235a4cf0bf25b8be88f011ea0fef46910865801000000000000007c2b2650d170087313955372306682dbcf2d95a2ca923a77e6b646a9a10bf83f01000000000000007a694fc030469cf01585fd29ff37905fbfc55329288d2fdd5d5294bc52e3bd2901000000000000009a78cffb3684ca2fd4118c4f885315179ecdbe2748d29066802c2e4529255b140100000000000000be9379477d3cce786682133454af701de1c04fa52cdcc5e9cf23ce47399fd8620100000000000000582545d042f1891e4c643dfa2c060cb6111072d88a0a64b45836e026ccf06e2d0100000000000000709f540a13dd7e28a612ad2df786d4c621ac6b748a768471f38c4a73d29665740100000000000000040000000000000002","babeFinalizedBlockWeight":143218,"finalizedBlockHeader":"0x6d788bb4c9f0af49bbe6357efbf70d159f6d4fde3c24ad9023d7371d82c5c8138aea2200c1c3c79ba04962d2de54cf8fa395c74721b7e2d52bda5e72ea508adfc048875a9235baba26634fc7689d21bfb4ffd4be7346435c5316634305f1de9b6a335e3c0c0642414245b50103220000005233bf1100000000a4fe8905054c283b73134bd53dd75973e97aff44ce0a4da7c5422c5ec882672893ecd2575b63eb7839c245557a7693f5068e9c2175036509e08453761e01680079eb6be89fbfcdfb08665ba15ecabb0452385930b33c84c3f57bc22aa1dc520f04424545468403b10deed59d1686b45ff6acbdbac827775dd18c559ff0729f80f5ed7ff31375f505424142450101a488fcdc503892633b7b264ba4155da3943a661def48f73fdb02fd55f2feff4b901374183a4fa5efb0532ca1351d7047a24f1daaa1057f1c7d0d3bf8c7bd628f","grandpaAuthoritySet":"0xe0731956aed8e45d4662a24769813130586e0beacbd233a2cdcef253e077189dc2010000000000000087cffd9a0057b40b259e26a23587cc4f0ba7eda8fb56a416a5ab9a07e73198ee0100000000000000a256c0ce61b51b44eb77967256f966c7acaf1882f3e7d4503e909f687472f5fa0100000000000000fcb7558928ec259bcbefc2c86933b25285c617ee92a577f960c575deae7843d60100000000000000560e7453678f6a9556c03099baae52506626aea490796d6bf8f5cfe476df178f0100000000000000c2ace1f4c2385ef4e02291e98b48c7486f66ed2595dafc31fddad638205713b101000000000000009bc095ad772bd23230116e09d3d852cb99c5ddaf06407ecb0eb2382bce6b329301000000000000008fabe46fdd0f4df9a72ce250df69dc52afd5e63cf44d2abf0ddde4b1fc2d556801000000000000009ad8f3fa4cbd30ec9c6299263ef17f35102cd1c91f9d607109c56b1136e754c10100000000000000fdcaf0312216d3a17495741a8873f40fc2f5e88a39a10b9fddea8badcf4b79a20100000000000000d7c36a347fc22ceee991dc9b36fe286a73ff89e308854e517006167da75fc3170100000000000000529fc18302e45ead6a64b64aa46fbbd1d3858f47ac80c397a96f1a5ed5a81c910100000000000000e4eb3771054e1b21343b4025aebffe990a3a27e934e94a0cc2a0e7571405980d01000000000000000882cd193bf1733a5f0ecbddc95ae901ade78888540d82b054b7053b4b8ba37a010000000000000093142c1ad13942529fc545a0e38f3588f3e30d71218a321fb9714a095b7fc6f70100000000000000e6343d0f175598bc4b04d9cc7e0f4eb529678a343ac633c3b71d28de46ce83290100000000000000a91582d0f43533536bf51243d912172fbb0deedbdd65d398d441fbea1cc4312701000000000000002e88c528b475d4c170d5b8784ce913e5247d3fc278d6a9a9a324ced318fba3270100000000000000f99c4c6c4bdadb511067a5a58381711aa9fc9aa13ad475facf19b69ba6d5044e01000000000000009092bb288257c8213436ad34ed56f52a0756ba9952de9a81f623a86dc6febe1301000000000000008542bafa4a04ecab4d17373a9f4b459c563383ab32d7bc7fffffe557c15f8a4a01000000000000007d71521963e9362d0739bb0161abd2ffbbe1ab3ab37ccdde9463b4048e619f520100000000000000a1d4f85f9b94ab61f9e16725cd485d90120560d7800229d94b9b71af5f5b4a1b0100000000000000785c5fd14b116d1ee9fe53a57e0cc079cf85e5388856ec2d8e847a997d3b5bd80100000000000000542e207bee9082df82dff1f5c9be749b8aa9460ed0a0ceb00e4c752cc3d27c0f01000000000000008e9db2ccde873464bc2888571806e01a9249cc029ad9ec9a69e1ebac5207f30d0100000000000000b329e7b2376dc74bd55b3fb85720d7fd2659c8c39f192c6223ba5364fc2cef6d010000000000000037a73ef7fd188625d364ea21abf8de2786affa8a53c0540589a747cea61554fb0100000000000000119a070623bc5088013c1a3f9037e177285c654c748136a4c6d85e03ca0e64e801000000000000000c4dc49538d77f88de5c5428a49ddcab2c22a9e3fc46fb6354371a13963d1a160100000000000000f71fedbcee2aa9020dec6152125ded3a003e3ff9afb0ce4e7272d0954efb55540100000000000000b6e24d45e4b33049391c302499c0767640460215d82296dd9f7e6d72f62dc5dc01000000000000002b67caacf2d33953b65e81376c0d8ec20d06ea9dd9ac3c3f83931a7a5f289ee70100000000000000555f0cd22b9d6c1f38c1e657be3cc01c50e012dfb681fe8a744d6b6dff240c0101000000000000006c93718ec5f383785592e07473ee84e8df1e4dcde5a034312e71449d83390a4601000000000000003303c858e5e0951701513cea5a56effc09b7187311af4b547c90fcbacc8640ca0100000000000000b305c9f25de702e741d9c4779fb60117ed9ad4e2cdb87600f9ab3552716bc85c0100000000000000a8b71728b343905f783dc9d48f76b5744e9d35bd23f6714b624c95ba377d2ad30100000000000000d0b2163d59ca2b471f19d6b9d33123d3b8246ed4ea1d492b539f3ef8fe3c8de20100000000000000a21a7e6d779926c637f0c215a2af86221ee9a172b793cc4802c7f068169429710100000000000000ffb607e86abd2f064effd16cd65ed64fe78132fdb16660a2446c8a2fd7d935430100000000000000fa7a89f756ce11028da1891fd8932e946e46618b70fef4cfabd6979cbe1e44580100000000000000186eac52af4c5615ac3939e3019d3b54f4b5074c935a3af17b20a5abf6dc37d20100000000000000e3395395ad6ec5d9db588fb76b0f1187186338aa63f4262879d3dc07cb682b2c0100000000000000464466b570cc823fad14249294d61fa4c4baf0367032018531dd900b0fa066110100000000000000a818de5fee03f9073ccb573d4b62619593ae3e6cebc8ee7a389f2b3825bfb20e0100000000000000d312f02ef7cfdb7cde8d55b75498393e36992e336a4fe93cae2417f35723f49c0100000000000000487d2d99b38ea7a09948b4df19ff56d591fc4f841f50e067e71052374c9f88e7010000000000000073aba74dd288d087b2167e459aaa57ebf34bd7e6c41ad21b764cc33f6ffe3e810100000000000000d68f6ab92ea94e4ab52cb536077509ce38c3fa61f2d7624673b6921456938adc0100000000000000dd5931ff339f8bdd063aaa8a61ed0d03f403c53d65844a8e4dc3fb8d4aeae0b80100000000000000727215f6ffc77d57437230a353e2d7834f43fc407cb44ac535b15aa3384e9c66010000000000000043ab7611c3d9d509c183041f9dac69bffec7301e955ee95013ae60e77fd49a640100000000000000810f02a6712ca2a711627c14d2c074639c43ae58d2de7796e878e6836175391e01000000000000008d4a84deae3b2168cd4970ae623059dd19e9c329acb2d3ac3d5212694b3d0e6501000000000000000334ac0f04797313a81f5301228a4f77f881564723178e7b199373fee1af9801010000000000000092000000000000000001a2ba080000490200000000000000005dcc000001000000000000000dd1000002000000000000006bda0000030000000000000023e60000040000000000000040f6000005000000000000005a0601000600000000000000c216010007000000000000002a2701000800000000000000553701000900000000000000834701000a00000000000000ab5701000b00000000000000d86701000c00000000000000508601000d00000000000000609401000e0000000000000070a201000f0000000000000078a90100100000000000000080b00100110000000000000090be01001200000000000000a0cc01001300000000000000b0da01001400000000000000c0e801001500000000000000d0f601001600000000000000e00402001700000000000000f01202001800000000000000002102001900000000000000102f02001a00000000000000203d02001b00000000000000304b02001c00000000000000405902001d00000000000000506702001e00000000000000607502001f00000000000000708302002000000000000000809102002100000000000000909f02002200000000000000a0ad02002300000000000000b0bb02002400000000000000c0c902002500000000000000d0d702002600000000000000e0e502002700000000000000f0f302002800000000000000ff01030029000000000000000f1003002a000000000000001f1e03002b000000000000002f2c03002c000000000000003f3a03002d000000000000004f4803002e000000000000005f5603002f000000000000006f64030030000000000000007f72030031000000000000008f80030032000000000000009f8e03003300000000000000af9c03003400000000000000bfaa03003500000000000000cfb803003600000000000000dfc603003700000000000000efd403003800000000000000ffe2030039000000000000000ff103003a000000000000001dff03003b000000000000002d0d04003c000000000000003d1b04003d000000000000004d2904003e000000000000005d3704003f000000000000006d45040040000000000000007d53040041000000000000008b61040042000000000000009b6f04004300000000000000867d040044000000000000004f8b04004500000000000000a08d0400460000000000000058990400470000000000000068a70400480000000000000078b50400490000000000000088c304004a0000000000000098d104004b00000000000000a4df04004c00000000000000b4ed04004d00000000000000c4fb04004e00000000000000d40905004f00000000000000e31705005000000000000000f32505005100000000000000033405005200000000000000134205005300000000000000235005005400000000000000335e05005500000000000000436c05005600000000000000537a0500570000000000000063880500580000000000000072960500590000000000000081a405005a0000000000000091b205005b00000000000000a1c005005c00000000000000b1ce05005d00000000000000c1dc05005e00000000000000d1ea05005f00000000000000e1f805006000000000000000f10606006100000000000000011506006200000000000000112306006300000000000000213106006400000000000000313f06006500000000000000414d06006600000000000000515b06006700000000000000616906006800000000000000717706006900000000000000818506006a00000000000000919306006b00000000000000a1a106006c00000000000000b1af06006d00000000000000c1bd06006e00000000000000d1cb06006f00000000000000e1d906007000000000000000f0e706007100000000000000fff5060072000000000000000f04070073000000000000001f12070074000000000000002f20070075000000000000003f2e070076000000000000004f3c070077000000000000005f4a070078000000000000006b58070079000000000000007b6607007a000000000000008b7407007b000000000000009b8207007c00000000000000ab9007007d00000000000000b59e07007e00000000000000a0ac07007f00000000000000b0ba07008000000000000000bfc807008100000000000000cfd607008200000000000000dfe407008300000000000000eff207008400000000000000fe00080085000000000000000e0f080086000000000000001e1d080087000000000000002e2b080088000000000000003e39080089000000000000004e4708008a000000000000005d5508008b00000000000000136308008c000000000000001f7108008d000000000000002f7f08008e000000000000003e8d08008f000000000000004e9b080090000000000000005ea9080091000000000000006eb70800"}} \ No newline at end of file diff --git a/rust/crates/truapi-provider/networks/previewnet-asset-hub.json b/rust/crates/truapi-provider/networks/previewnet-asset-hub.json new file mode 100644 index 000000000..26b0be04e --- /dev/null +++ b/rust/crates/truapi-provider/networks/previewnet-asset-hub.json @@ -0,0 +1 @@ +{"name":"Asset Hub Local","id":"asset-hub-local","chainType":"Local","bootNodes":["/dns4/previewnet.substrate.dev/tcp/31335/wss/p2p/12D3KooWFAk8DMwHtU7Xaf1c5wbPw1sHNa9871o8F3DYJmt2e7Uc"],"telemetryEndpoints":null,"protocolId":null,"properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"relay_chain":"paseo-local","para_id":1500,"codeSubstitutes":{},"genesis":{"stateRootHash":"0xf04dc8807c6e006b96ff3bb36f3fc00e20ec483ba6cd2ed97df779c8d1a25f99"}} \ No newline at end of file diff --git a/rust/crates/truapi-provider/networks/previewnet-bulletin.json b/rust/crates/truapi-provider/networks/previewnet-bulletin.json new file mode 100644 index 000000000..44745a804 --- /dev/null +++ b/rust/crates/truapi-provider/networks/previewnet-bulletin.json @@ -0,0 +1 @@ +{"bootNodes":["/dns4/previewnet.substrate.dev/tcp/31333/wss/p2p/12D3KooWB9ykzLTAViWXvkyCLy9BRY1nhGjCFnR1pPXxU8ASvRJa"],"chainType":"Local","codeSubstitutes":{},"genesis":{"stateRootHash":"0x61601c7f22aee70febf74b0d747f08ac091ea8661f488839120b738b5c81dd5a"},"id":"bulletin-local","name":"Bulletin Local","para_id":1501,"properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"protocolId":null,"relay_chain":"paseo-local","telemetryEndpoints":null} \ No newline at end of file diff --git a/rust/crates/truapi-provider/networks/previewnet-people.json b/rust/crates/truapi-provider/networks/previewnet-people.json new file mode 100644 index 000000000..995960fb0 --- /dev/null +++ b/rust/crates/truapi-provider/networks/previewnet-people.json @@ -0,0 +1 @@ +{"bootNodes":["/dns4/previewnet.substrate.dev/tcp/31336/wss/p2p/12D3KooWDKiYC3apkS1F6nR7rUwrv5usxKM9eJK1qjTDLpXVK2f3"],"chainType":"Local","codeSubstitutes":{},"genesis":{"stateRootHash":"0xa2a0e33e918e6ecaf54ceba35ba428eb576c9862355a398024c25ed728b03f4b"},"id":"individuality-local","name":"Individuality Local","para_id":1502,"properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"protocolId":null,"relay_chain":"paseo-local","telemetryEndpoints":null} \ No newline at end of file diff --git a/rust/crates/truapi-provider/networks/previewnet.json b/rust/crates/truapi-provider/networks/previewnet.json new file mode 100644 index 000000000..558d1f16c --- /dev/null +++ b/rust/crates/truapi-provider/networks/previewnet.json @@ -0,0 +1 @@ +{"bootNodes":["/dns4/previewnet.substrate.dev/tcp/31334/wss/p2p/12D3KooWLb22AADS9TuJPw8meJayuKC1vDXrJy8pApeTDYyzyGHd"],"chainType":"Local","codeSubstitutes":{},"genesis":{"stateRootHash":"0xdf2e11e88e6bc47798689cc61cba135501e9de5310d952064f407d5403406d39"},"id":"paseo-local","name":"Paseo Local","properties":{"tokenDecimals":10,"tokenSymbol":"PAS"},"protocolId":null,"telemetryEndpoints":null,"lightSyncState":{"babeEpochChanges":"0x04dd06146b5dd23a45f5aea810bd56199ada6f51a6c4d3666d16391e9801e1cab6f5e50200014533bf11000000004f33bf110000000004f5a8257b40e05bba305e2dc6b4c38af7fb349bfd367034e5ac547d3131f18da3ffe50200014f33bf11000000005933bf11000000000855521d5062be1e3e3111e83694b4dca702de7e7f183a112df9b891479b3cce6b09e60200015933bf11000000006333bf11000000000017b60ad313249ed404f90d7d02b771baf1a99004ebfcda48ab1b522dc02f6da109e60200015933bf11000000006333bf110000000000001017b60ad313249ed404f90d7d02b771baf1a99004ebfcda48ab1b522dc02f6da109e6020001364a0000000000005933bf11000000000a00000000000000184c33d10f00eef3eb6e38babc5c9702ac04e679731bda3b91b11e3d7c8648a23301000000000000000e473d99542d46c426c28ed434dede9c1823e055f3753a24d6e533093c98ca550100000000000000ea2503f6edada5718b2128b275aaed5e996abd8b13d3c5b5d0e4d3b86bfb750901000000000000003abfafb72d8ded312d8a0ae8349477182b5e6252d14b57ba485f5554b07a51100100000000000000ca0f4ece2be5b1cb4fd0b8ec6d43794ea156d6cce565630278350a630d61bc090100000000000000d4727e1d4dd610aba7be553ddb6aa664ffdf1a0ff9e1062183430e4a03997c17010000000000000065a54801efefea962a11ec35c8ffb4cee83e0bfee445c6a93f968651d0bcd1f7010000000000000004000000000000000255521d5062be1e3e3111e83694b4dca702de7e7f183a112df9b891479b3cce6b09e6020001364a0000000000005933bf11000000000a00000000000000184c33d10f00eef3eb6e38babc5c9702ac04e679731bda3b91b11e3d7c8648a23301000000000000000e473d99542d46c426c28ed434dede9c1823e055f3753a24d6e533093c98ca550100000000000000ea2503f6edada5718b2128b275aaed5e996abd8b13d3c5b5d0e4d3b86bfb750901000000000000003abfafb72d8ded312d8a0ae8349477182b5e6252d14b57ba485f5554b07a51100100000000000000ca0f4ece2be5b1cb4fd0b8ec6d43794ea156d6cce565630278350a630d61bc090100000000000000d4727e1d4dd610aba7be553ddb6aa664ffdf1a0ff9e1062183430e4a03997c17010000000000000065a54801efefea962a11ec35c8ffb4cee83e0bfee445c6a93f968651d0bcd1f70100000000000000040000000000000002dd06146b5dd23a45f5aea810bd56199ada6f51a6c4d3666d16391e9801e1cab6f5e5020001344a0000000000004533bf11000000000a00000000000000184c33d10f00eef3eb6e38babc5c9702ac04e679731bda3b91b11e3d7c8648a23301000000000000000e473d99542d46c426c28ed434dede9c1823e055f3753a24d6e533093c98ca550100000000000000ea2503f6edada5718b2128b275aaed5e996abd8b13d3c5b5d0e4d3b86bfb750901000000000000003abfafb72d8ded312d8a0ae8349477182b5e6252d14b57ba485f5554b07a51100100000000000000ca0f4ece2be5b1cb4fd0b8ec6d43794ea156d6cce565630278350a630d61bc090100000000000000d4727e1d4dd610aba7be553ddb6aa664ffdf1a0ff9e1062183430e4a03997c170100000000000000c8086b65db54bd80293a7e5da248f3fb8b3fd3ec81e9a5d7147f0aa0ec59adbc0100000000000000040000000000000002f5a8257b40e05bba305e2dc6b4c38af7fb349bfd367034e5ac547d3131f18da3ffe5020001354a0000000000004f33bf11000000000a00000000000000184c33d10f00eef3eb6e38babc5c9702ac04e679731bda3b91b11e3d7c8648a23301000000000000000e473d99542d46c426c28ed434dede9c1823e055f3753a24d6e533093c98ca550100000000000000ea2503f6edada5718b2128b275aaed5e996abd8b13d3c5b5d0e4d3b86bfb750901000000000000003abfafb72d8ded312d8a0ae8349477182b5e6252d14b57ba485f5554b07a51100100000000000000ca0f4ece2be5b1cb4fd0b8ec6d43794ea156d6cce565630278350a630d61bc090100000000000000d4727e1d4dd610aba7be553ddb6aa664ffdf1a0ff9e1062183430e4a03997c170100000000000000cb25b8ed275b263d815413a5be0a6c0cc38e5455b746fd08cfd26302792bd35b0100000000000000040000000000000002","babeFinalizedBlockWeight":47020,"finalizedBlockHeader":"0x76c7f1451acd9691ce6a7f60e8c442fac87a7de74b2763a3ed50cc76abf509cf36980b00f3b6f274c4d20d34838a85bb5c29752255881ef49f869674ee07857e2c77c87d195da82fc02a92182daa4c6d6ff91f5bfb5447866244f003bb46bad550d791210c0642414245b50101020000005333bf11000000005adfaf8c87507fa7b9f7fedd84391728056308d16143631c094b0c69232f4a04d0756951dcc70482acec78525073bea10df91311237a2739a39c17083e942809fb72ea3e914f8892825a9d4e7871188101ad2a7c4d0fb8608b758b7213ca3c060442454546840335c63b5e7482418c6aa6bdbb5e9b50d51245a028876840ef38ae7f7ca04d2f530542414245010116edd30ebd28c5a7a27cdd71dc0044d788826482b70a71c6a5517a80aebc8d1d8e2ae899c762e2e4ff3135c763b177ca0aeebfa646d687dcbd21d9d03e1a9e81","grandpaAuthoritySet":"0x186b90274712bc1d5cceb335c31f5dbb27be382e4644446ee9d4f57a389340eaf6010000000000000053c81c5a5513a9f413a0995cf784842969bd0009841fdca97d71a202361f2a040100000000000000ca94d4641bf9d446d448d3defac85b67e76599e491b2a7662c5b53b9145c2d42010000000000000065ac206a09197ec5ac68bd05fe0229dd1fd0945f16fd7325355d3725af6794960100000000000000a0b00b9a2caef50e40cad0a14b06c140f5ef8fe2f09b168c46f7e256b5a4cba20100000000000000538d231e1feb34ce3c6db652ac5056365bb5d7ac1fd6fded19abdb4bb7b298190100000000000000344a00000000000000010de6020000d228010000000000000000001500000001000000000000001f00000002000000000000002900000003000000000000003300000004000000000000003d00000005000000000000004700000006000000000000005100000007000000000000005b00000008000000000000006500000009000000000000006f0000000a00000000000000790000000b00000000000000830000000c000000000000008d0000000d00000000000000970000000e00000000000000a10000000f00000000000000ab0000001000000000000000b50000001100000000000000bf0000001200000000000000c90000001300000000000000d30000001400000000000000dd0000001500000000000000e70000001600000000000000f10000001700000000000000fb00000018000000000000000501000019000000000000000f0100001a00000000000000190100001b00000000000000230100001c000000000000002d0100001d00000000000000370100001e00000000000000410100001f000000000000004b01000020000000000000005501000021000000000000005f01000022000000000000006901000023000000000000007301000024000000000000007d01000025000000000000008701000026000000000000009101000027000000000000009b0100002800000000000000a50100002900000000000000af0100002a00000000000000b90100002b00000000000000c30100002c00000000000000cd0100002d00000000000000d70100002e00000000000000e10100002f00000000000000eb0100003000000000000000f50100003100000000000000ff01000032000000000000000902000033000000000000001302000034000000000000001d02000035000000000000002702000036000000000000003102000037000000000000003b02000038000000000000004502000039000000000000004f0200003a00000000000000590200003b00000000000000630200003c000000000000006d0200003d00000000000000770200003e00000000000000810200003f000000000000008b02000040000000000000009502000041000000000000009f0200004200000000000000a90200004300000000000000b30200004400000000000000bd0200004500000000000000c70200004600000000000000d10200004700000000000000db0200004800000000000000e50200004900000000000000ef0200004a00000000000000f90200004b00000000000000030300004c000000000000000d0300004d00000000000000170300004e00000000000000210300004f000000000000002b03000050000000000000003503000051000000000000003f03000052000000000000004903000053000000000000005303000054000000000000005d03000055000000000000006703000056000000000000007103000057000000000000007b03000058000000000000008503000059000000000000008f0300005a00000000000000990300005b00000000000000a30300005c00000000000000ad0300005d00000000000000b70300005e00000000000000c10300005f00000000000000cb0300006000000000000000d50300006100000000000000df0300006200000000000000e90300006300000000000000f30300006400000000000000fd03000065000000000000000704000066000000000000001104000067000000000000001b04000068000000000000002504000069000000000000002f0400006a00000000000000390400006b00000000000000430400006c000000000000004d0400006d00000000000000570400006e00000000000000610400006f000000000000006b04000070000000000000007504000071000000000000007f04000072000000000000008904000073000000000000009304000074000000000000009d0400007500000000000000a70400007600000000000000b10400007700000000000000bb0400007800000000000000c50400007900000000000000cf0400007a00000000000000d90400007b00000000000000e30400007c00000000000000ed0400007d00000000000000f70400007e00000000000000010500007f000000000000000b05000080000000000000001505000081000000000000001f05000082000000000000002905000083000000000000003305000084000000000000003d05000085000000000000004705000086000000000000005105000087000000000000005b05000088000000000000006505000089000000000000006f0500008a00000000000000790500008b00000000000000830500008c000000000000008d0500008d00000000000000970500008e00000000000000a10500008f00000000000000ab0500009000000000000000b50500009100000000000000bf0500009200000000000000c90500009300000000000000d30500009400000000000000dd0500009500000000000000e70500009600000000000000f10500009700000000000000fb05000098000000000000000506000099000000000000000f0600009a00000000000000190600009b00000000000000230600009c000000000000002d0600009d00000000000000370600009e00000000000000410600009f000000000000004b060000a00000000000000055060000a1000000000000005f060000a20000000000000069060000a30000000000000073060000a4000000000000007d060000a50000000000000087060000a60000000000000091060000a7000000000000009b060000a800000000000000a5060000a900000000000000af060000aa00000000000000b9060000ab00000000000000c3060000ac00000000000000cd060000ad00000000000000d7060000ae00000000000000e1060000af00000000000000eb060000b000000000000000f5060000b100000000000000ff060000b20000000000000009070000b30000000000000013070000b4000000000000001d070000b50000000000000027070000b60000000000000031070000b7000000000000003b070000b80000000000000045070000b9000000000000004f070000ba0000000000000059070000bb0000000000000063070000bc000000000000006d070000bd0000000000000077070000be0000000000000081070000bf000000000000008b070000c00000000000000095070000c1000000000000009f070000c200000000000000a9070000c300000000000000b3070000c400000000000000bd070000c500000000000000c7070000c600000000000000d1070000c700000000000000db070000c800000000000000e5070000c900000000000000ef070000ca00000000000000f9070000cb0000000000000003080000cc000000000000000d080000cd0000000000000017080000ce0000000000000021080000cf000000000000002b080000d00000000000000035080000d1000000000000003f080000d20000000000000049080000d30000000000000053080000d4000000000000005d080000d50000000000000067080000d60000000000000071080000d7000000000000007b080000d80000000000000085080000d9000000000000008f080000da0000000000000099080000db00000000000000a3080000dc00000000000000ad080000dd00000000000000b7080000de00000000000000c1080000df00000000000000cb080000e000000000000000d5080000e100000000000000df080000e200000000000000e9080000e300000000000000f3080000e400000000000000fd080000e50000000000000007090000e60000000000000011090000e7000000000000001b090000e80000000000000025090000e9000000000000002f090000ea0000000000000039090000eb0000000000000043090000ec000000000000004d090000ed0000000000000057090000ee0000000000000061090000ef000000000000006b090000f00000000000000075090000f1000000000000007f090000f20000000000000089090000f30000000000000093090000f4000000000000009d090000f500000000000000a7090000f600000000000000b1090000f700000000000000bb090000f800000000000000c5090000f900000000000000cf090000fa00000000000000d9090000fb00000000000000e3090000fc00000000000000ed090000fd00000000000000f7090000fe00000000000000010a0000ff000000000000000b0a00000001000000000000150a000001010000000000001f0a00000201000000000000290a00000301000000000000330a000004010000000000003d0a00000501000000000000470a00000601000000000000510a000007010000000000005b0a00000801000000000000650a000009010000000000006f0a00000a01000000000000790a00000b01000000000000830a00000c010000000000008d0a00000d01000000000000970a00000e01000000000000a10a00000f01000000000000ab0a00001001000000000000b50a00001101000000000000bf0a00001201000000000000c90a00001301000000000000d30a00001401000000000000dd0a00001501000000000000e70a00001601000000000000f10a00001701000000000000fb0a00001801000000000000050b000019010000000000000f0b00001a01000000000000190b00001b01000000000000230b00001c010000000000002d0b00001d01000000000000370b00001e01000000000000410b00001f010000000000004b0b00002001000000000000550b000021010000000000005f0b00002201000000000000690b00002301000000000000730b000024010000000000007d0b00002501000000000000870b00002601000000000000910b000027010000000000009b0b00002801000000000000a50b00002901000000000000af0b00002a01000000000000b90b00002b01000000000000c30b00002c01000000000000cd0b00002d01000000000000d70b00002e01000000000000e10b00002f01000000000000eb0b00003001000000000000f50b00003101000000000000ff0b00003201000000000000090c00003301000000000000130c000034010000000000001d0c00003501000000000000270c00003601000000000000310c000037010000000000003b0c00003801000000000000450c000039010000000000004f0c00003a01000000000000590c00003b01000000000000630c00003c010000000000006d0c00003d01000000000000770c00003e01000000000000810c00003f010000000000008b0c00004001000000000000950c000041010000000000009f0c00004201000000000000a90c00004301000000000000b30c00004401000000000000bd0c00004501000000000000c70c00004601000000000000d10c00004701000000000000db0c00004801000000000000e50c00004901000000000000ef0c00004a01000000000000f90c00004b01000000000000030d00004c010000000000000d0d00004d01000000000000170d00004e01000000000000210d00004f010000000000002b0d00005001000000000000350d000051010000000000003f0d00005201000000000000490d00005301000000000000530d000054010000000000005d0d00005501000000000000670d00005601000000000000710d000057010000000000007b0d00005801000000000000850d000059010000000000008f0d00005a01000000000000990d00005b01000000000000a30d00005c01000000000000ad0d00005d01000000000000b70d00005e01000000000000c10d00005f01000000000000cb0d00006001000000000000d50d00006101000000000000df0d00006201000000000000e90d00006301000000000000f30d00006401000000000000fd0d00006501000000000000070e00006601000000000000110e000067010000000000001b0e00006801000000000000250e000069010000000000002f0e00006a01000000000000390e00006b01000000000000430e00006c010000000000004d0e00006d01000000000000570e00006e01000000000000610e00006f010000000000006b0e00007001000000000000750e000071010000000000007f0e00007201000000000000890e00007301000000000000930e000074010000000000009d0e00007501000000000000a70e00007601000000000000b10e00007701000000000000bb0e00007801000000000000c50e00007901000000000000cf0e00007a01000000000000d90e00007b01000000000000e30e00007c01000000000000ed0e00007d01000000000000f70e00007e01000000000000010f00007f010000000000000b0f00008001000000000000150f000081010000000000001f0f00008201000000000000290f00008301000000000000330f000084010000000000003d0f00008501000000000000470f00008601000000000000510f000087010000000000005b0f00008801000000000000650f000089010000000000006f0f00008a01000000000000790f00008b01000000000000830f00008c010000000000008d0f00008d01000000000000970f00008e01000000000000a10f00008f01000000000000ab0f00009001000000000000b50f00009101000000000000bf0f00009201000000000000c90f00009301000000000000d30f00009401000000000000dd0f00009501000000000000e70f00009601000000000000f10f00009701000000000000fb0f000098010000000000000510000099010000000000000f1000009a01000000000000191000009b01000000000000231000009c010000000000002d1000009d01000000000000371000009e01000000000000411000009f010000000000004b100000a00100000000000055100000a1010000000000005f100000a20100000000000069100000a30100000000000073100000a4010000000000007d100000a50100000000000087100000a60100000000000091100000a7010000000000009b100000a801000000000000a5100000a901000000000000af100000aa01000000000000b9100000ab01000000000000c3100000ac01000000000000cd100000ad01000000000000d7100000ae01000000000000e1100000af01000000000000eb100000b001000000000000f5100000b101000000000000ff100000b20100000000000009110000b30100000000000013110000b4010000000000001d110000b50100000000000027110000b60100000000000031110000b7010000000000003b110000b80100000000000045110000b9010000000000004f110000ba0100000000000059110000bb0100000000000063110000bc010000000000006d110000bd0100000000000077110000be0100000000000081110000bf010000000000008b110000c00100000000000095110000c1010000000000009f110000c201000000000000a9110000c301000000000000b3110000c401000000000000bd110000c501000000000000c7110000c601000000000000d1110000c701000000000000db110000c801000000000000e5110000c901000000000000ef110000ca01000000000000f9110000cb0100000000000003120000cc010000000000000d120000cd0100000000000017120000ce0100000000000021120000cf010000000000002b120000d00100000000000035120000d1010000000000003f120000d20100000000000049120000d30100000000000053120000d4010000000000005d120000d50100000000000067120000d60100000000000071120000d7010000000000007b120000d80100000000000085120000d9010000000000008f120000da0100000000000099120000db01000000000000a3120000dc01000000000000ad120000dd01000000000000b7120000de01000000000000c1120000df01000000000000cb120000e001000000000000d5120000e101000000000000df120000e201000000000000e9120000e301000000000000f3120000e401000000000000fd120000e50100000000000007130000e60100000000000011130000e7010000000000001b130000e80100000000000025130000e9010000000000002f130000ea0100000000000039130000eb0100000000000043130000ec010000000000004d130000ed0100000000000057130000ee0100000000000061130000ef010000000000006b130000f00100000000000075130000f1010000000000007f130000f20100000000000089130000f30100000000000093130000f4010000000000009d130000f501000000000000a7130000f601000000000000b1130000f701000000000000bb130000f801000000000000c5130000f901000000000000cf130000fa01000000000000d9130000fb01000000000000e3130000fc01000000000000ed130000fd01000000000000f7130000fe0100000000000001140000ff010000000000000b14000000020000000000001514000001020000000000001f14000002020000000000002914000003020000000000003314000004020000000000003d14000005020000000000004714000006020000000000005114000007020000000000005b14000008020000000000006514000009020000000000006f1400000a02000000000000791400000b02000000000000831400000c020000000000008d1400000d02000000000000971400000e02000000000000a11400000f02000000000000ab1400001002000000000000b51400001102000000000000bf1400001202000000000000c91400001302000000000000d31400001402000000000000dd1400001502000000000000e71400001602000000000000f11400001702000000000000fb14000018020000000000000515000019020000000000000f1500001a02000000000000191500001b02000000000000231500001c020000000000002d1500001d02000000000000371500001e02000000000000411500001f020000000000004b15000020020000000000005515000021020000000000005f15000022020000000000006915000023020000000000007315000024020000000000007d15000025020000000000008715000026020000000000009115000027020000000000009b1500002802000000000000a51500002902000000000000af1500002a02000000000000b91500002b02000000000000c31500002c02000000000000cd1500002d02000000000000d71500002e02000000000000e11500002f02000000000000eb1500003002000000000000f51500003102000000000000ff15000032020000000000000916000033020000000000001316000034020000000000001d16000035020000000000002716000036020000000000003116000037020000000000003b16000038020000000000004516000039020000000000004f1600003a02000000000000591600003b02000000000000631600003c020000000000006d1600003d02000000000000771600003e02000000000000811600003f020000000000008b16000040020000000000009516000041020000000000009f1600004202000000000000a91600004302000000000000b31600004402000000000000bd1600004502000000000000c71600004602000000000000d11600004702000000000000db1600004802000000000000e51600004902000000000000ef1600004a02000000000000f91600004b02000000000000031700004c020000000000000d1700004d02000000000000171700004e02000000000000211700004f020000000000002b17000050020000000000003517000051020000000000003f17000052020000000000004917000053020000000000005317000054020000000000005d17000055020000000000006717000056020000000000007117000057020000000000007b17000058020000000000008517000059020000000000008f1700005a02000000000000991700005b02000000000000a31700005c02000000000000ad1700005d02000000000000b71700005e02000000000000c11700005f02000000000000cb1700006002000000000000d51700006102000000000000df1700006202000000000000e91700006302000000000000f31700006402000000000000fd17000065020000000000000718000066020000000000001118000067020000000000001b18000068020000000000002518000069020000000000002f1800006a02000000000000391800006b02000000000000431800006c020000000000004d1800006d02000000000000571800006e02000000000000611800006f020000000000006b18000070020000000000007518000071020000000000007f18000072020000000000008918000073020000000000009318000074020000000000009d1800007502000000000000a71800007602000000000000b11800007702000000000000bb1800007802000000000000c51800007902000000000000cf1800007a02000000000000d91800007b02000000000000e31800007c02000000000000ed1800007d02000000000000f71800007e02000000000000011900007f020000000000000b19000080020000000000001519000081020000000000001f19000082020000000000002919000083020000000000003319000084020000000000003d19000085020000000000004719000086020000000000005119000087020000000000005b19000088020000000000006519000089020000000000006f1900008a02000000000000791900008b02000000000000831900008c020000000000008d1900008d02000000000000971900008e02000000000000a11900008f02000000000000ab1900009002000000000000b51900009102000000000000bf1900009202000000000000c91900009302000000000000d31900009402000000000000dd1900009502000000000000e71900009602000000000000f11900009702000000000000fb1900009802000000000000051a000099020000000000000f1a00009a02000000000000191a00009b02000000000000231a00009c020000000000002d1a00009d02000000000000371a00009e02000000000000411a00009f020000000000004b1a0000a002000000000000551a0000a1020000000000005f1a0000a202000000000000691a0000a302000000000000731a0000a4020000000000007d1a0000a502000000000000871a0000a602000000000000911a0000a7020000000000009b1a0000a802000000000000a51a0000a902000000000000af1a0000aa02000000000000b91a0000ab02000000000000c31a0000ac02000000000000cd1a0000ad02000000000000d71a0000ae02000000000000e11a0000af02000000000000eb1a0000b002000000000000f51a0000b102000000000000ff1a0000b202000000000000091b0000b302000000000000131b0000b4020000000000001d1b0000b502000000000000271b0000b602000000000000311b0000b7020000000000003b1b0000b802000000000000451b0000b9020000000000004f1b0000ba02000000000000591b0000bb02000000000000631b0000bc020000000000006d1b0000bd02000000000000771b0000be02000000000000811b0000bf020000000000008b1b0000c002000000000000951b0000c1020000000000009f1b0000c202000000000000a91b0000c302000000000000b31b0000c402000000000000bd1b0000c502000000000000c71b0000c602000000000000d11b0000c702000000000000db1b0000c802000000000000e51b0000c902000000000000ef1b0000ca02000000000000f91b0000cb02000000000000031c0000cc020000000000000d1c0000cd02000000000000171c0000ce02000000000000211c0000cf020000000000002b1c0000d002000000000000351c0000d1020000000000003f1c0000d202000000000000491c0000d302000000000000531c0000d4020000000000005d1c0000d502000000000000671c0000d602000000000000711c0000d7020000000000007b1c0000d802000000000000851c0000d9020000000000008f1c0000da02000000000000991c0000db02000000000000a31c0000dc02000000000000ad1c0000dd02000000000000b71c0000de02000000000000c11c0000df02000000000000cb1c0000e002000000000000d51c0000e102000000000000df1c0000e202000000000000e91c0000e302000000000000f31c0000e402000000000000fd1c0000e502000000000000071d0000e602000000000000111d0000e7020000000000001b1d0000e802000000000000251d0000e9020000000000002f1d0000ea02000000000000391d0000eb02000000000000431d0000ec020000000000004d1d0000ed02000000000000571d0000ee02000000000000611d0000ef020000000000006b1d0000f002000000000000751d0000f1020000000000007f1d0000f202000000000000891d0000f302000000000000931d0000f4020000000000009d1d0000f502000000000000a71d0000f602000000000000b11d0000f702000000000000bb1d0000f802000000000000c51d0000f902000000000000cf1d0000fa02000000000000d91d0000fb02000000000000e31d0000fc02000000000000ed1d0000fd02000000000000f71d0000fe02000000000000011e0000ff020000000000000b1e00000003000000000000151e000001030000000000001f1e00000203000000000000291e00000303000000000000331e000004030000000000003d1e00000503000000000000471e00000603000000000000511e000007030000000000005b1e00000803000000000000651e000009030000000000006f1e00000a03000000000000791e00000b03000000000000831e00000c030000000000008d1e00000d03000000000000971e00000e03000000000000a11e00000f03000000000000ab1e00001003000000000000b51e00001103000000000000bf1e00001203000000000000c91e00001303000000000000d31e00001403000000000000dd1e00001503000000000000e71e00001603000000000000f11e00001703000000000000fb1e00001803000000000000051f000019030000000000000f1f00001a03000000000000191f00001b03000000000000231f00001c030000000000002d1f00001d03000000000000371f00001e03000000000000411f00001f030000000000004b1f00002003000000000000551f000021030000000000005f1f00002203000000000000691f00002303000000000000731f000024030000000000007d1f00002503000000000000871f00002603000000000000911f000027030000000000009b1f00002803000000000000a51f00002903000000000000af1f00002a03000000000000b91f00002b03000000000000c31f00002c03000000000000cd1f00002d03000000000000d71f00002e03000000000000e11f00002f03000000000000eb1f00003003000000000000f51f00003103000000000000ff1f000032030000000000000920000033030000000000001320000034030000000000001d20000035030000000000002720000036030000000000003120000037030000000000003b20000038030000000000004520000039030000000000004f2000003a03000000000000592000003b03000000000000632000003c030000000000006d2000003d03000000000000772000003e03000000000000812000003f030000000000008b20000040030000000000009520000041030000000000009f2000004203000000000000a92000004303000000000000b32000004403000000000000bd2000004503000000000000c72000004603000000000000d12000004703000000000000db2000004803000000000000e52000004903000000000000ef2000004a03000000000000f92000004b03000000000000032100004c030000000000000d2100004d03000000000000172100004e03000000000000212100004f030000000000002b21000050030000000000003521000051030000000000003f21000052030000000000004921000053030000000000005321000054030000000000005d21000055030000000000006721000056030000000000007121000057030000000000007b21000058030000000000008521000059030000000000008f2100005a03000000000000992100005b03000000000000a32100005c03000000000000ad2100005d03000000000000b72100005e03000000000000c12100005f03000000000000cb2100006003000000000000d52100006103000000000000df2100006203000000000000e92100006303000000000000f32100006403000000000000fd21000065030000000000000722000066030000000000001122000067030000000000001b22000068030000000000002522000069030000000000002f2200006a03000000000000392200006b03000000000000432200006c030000000000004d2200006d03000000000000572200006e03000000000000612200006f030000000000006b22000070030000000000007522000071030000000000007f22000072030000000000008922000073030000000000009322000074030000000000009d2200007503000000000000a72200007603000000000000b12200007703000000000000bb2200007803000000000000c52200007903000000000000cf2200007a03000000000000d92200007b03000000000000e32200007c03000000000000ed2200007d03000000000000f72200007e03000000000000012300007f030000000000000b23000080030000000000001523000081030000000000001f23000082030000000000002923000083030000000000003323000084030000000000003d23000085030000000000004723000086030000000000005123000087030000000000005b23000088030000000000006523000089030000000000006f2300008a03000000000000792300008b03000000000000832300008c030000000000008d2300008d03000000000000972300008e03000000000000a12300008f03000000000000ab2300009003000000000000b52300009103000000000000bf2300009203000000000000c92300009303000000000000d32300009403000000000000dd2300009503000000000000e72300009603000000000000f12300009703000000000000fb23000098030000000000000524000099030000000000000f2400009a03000000000000192400009b03000000000000232400009c030000000000002d2400009d03000000000000372400009e03000000000000412400009f030000000000004b240000a00300000000000055240000a1030000000000005f240000a20300000000000069240000a30300000000000073240000a4030000000000007d240000a50300000000000087240000a60300000000000091240000a7030000000000009b240000a803000000000000a5240000a903000000000000af240000aa03000000000000b9240000ab03000000000000c3240000ac03000000000000cd240000ad03000000000000d7240000ae03000000000000e1240000af03000000000000eb240000b003000000000000f5240000b103000000000000ff240000b20300000000000009250000b30300000000000013250000b4030000000000001d250000b50300000000000027250000b60300000000000031250000b7030000000000003b250000b80300000000000045250000b9030000000000004f250000ba0300000000000059250000bb0300000000000063250000bc030000000000006d250000bd0300000000000077250000be0300000000000081250000bf030000000000008b250000c00300000000000095250000c1030000000000009f250000c203000000000000a9250000c303000000000000b3250000c403000000000000bd250000c503000000000000c7250000c603000000000000d1250000c703000000000000db250000c803000000000000e5250000c903000000000000ef250000ca03000000000000f9250000cb0300000000000003260000cc030000000000000d260000cd0300000000000017260000ce0300000000000021260000cf030000000000002b260000d00300000000000035260000d1030000000000003f260000d20300000000000049260000d30300000000000053260000d4030000000000005d260000d50300000000000067260000d60300000000000071260000d7030000000000007b260000d80300000000000085260000d9030000000000008f260000da0300000000000099260000db03000000000000a3260000dc03000000000000ad260000dd03000000000000b7260000de03000000000000c1260000df03000000000000cb260000e003000000000000d5260000e103000000000000df260000e203000000000000e9260000e303000000000000f3260000e403000000000000fd260000e50300000000000007270000e60300000000000011270000e7030000000000001b270000e80300000000000025270000e9030000000000002f270000ea0300000000000039270000eb0300000000000043270000ec030000000000004d270000ed0300000000000057270000ee0300000000000061270000ef030000000000006b270000f00300000000000075270000f1030000000000007f270000f20300000000000089270000f30300000000000093270000f4030000000000009d270000f503000000000000a7270000f603000000000000b1270000f703000000000000bb270000f803000000000000c5270000f903000000000000cf270000fa03000000000000d9270000fb03000000000000e3270000fc03000000000000ed270000fd03000000000000f7270000fe0300000000000001280000ff030000000000000b28000000040000000000001528000001040000000000001f28000002040000000000002928000003040000000000003328000004040000000000003d28000005040000000000004728000006040000000000005128000007040000000000005b28000008040000000000006528000009040000000000006f2800000a04000000000000792800000b04000000000000832800000c040000000000008d2800000d04000000000000972800000e04000000000000a12800000f04000000000000ab2800001004000000000000b52800001104000000000000bf2800001204000000000000c92800001304000000000000d32800001404000000000000dd2800001504000000000000e72800001604000000000000f12800001704000000000000fb28000018040000000000000529000019040000000000000f2900001a04000000000000192900001b04000000000000232900001c040000000000002d2900001d04000000000000372900001e04000000000000412900001f040000000000004b29000020040000000000005529000021040000000000005f29000022040000000000006929000023040000000000007329000024040000000000007d29000025040000000000008729000026040000000000009129000027040000000000009b2900002804000000000000a52900002904000000000000af2900002a04000000000000b92900002b04000000000000c32900002c04000000000000cd2900002d04000000000000d72900002e04000000000000e12900002f04000000000000eb2900003004000000000000f52900003104000000000000ff2900003204000000000000092a00003304000000000000132a000034040000000000001d2a00003504000000000000272a00003604000000000000312a000037040000000000003b2a00003804000000000000452a000039040000000000004f2a00003a04000000000000592a00003b04000000000000632a00003c040000000000006d2a00003d04000000000000772a00003e04000000000000812a00003f040000000000008b2a00004004000000000000952a000041040000000000009f2a00004204000000000000a92a00004304000000000000b32a00004404000000000000bd2a00004504000000000000c72a00004604000000000000d12a00004704000000000000db2a00004804000000000000e52a00004904000000000000ef2a00004a04000000000000f92a00004b04000000000000032b00004c040000000000000d2b00004d04000000000000172b00004e04000000000000212b00004f040000000000002b2b00005004000000000000352b000051040000000000003f2b00005204000000000000492b00005304000000000000532b000054040000000000005d2b00005504000000000000672b00005604000000000000712b000057040000000000007b2b00005804000000000000852b000059040000000000008f2b00005a04000000000000992b00005b04000000000000a32b00005c04000000000000ad2b00005d04000000000000b72b00005e04000000000000c12b00005f04000000000000cb2b00006004000000000000d52b00006104000000000000df2b00006204000000000000e92b00006304000000000000f32b00006404000000000000fd2b00006504000000000000072c00006604000000000000112c000067040000000000001b2c00006804000000000000252c000069040000000000002f2c00006a04000000000000392c00006b04000000000000432c00006c040000000000004d2c00006d04000000000000572c00006e04000000000000612c00006f040000000000006b2c00007004000000000000752c000071040000000000007f2c00007204000000000000892c00007304000000000000932c000074040000000000009d2c00007504000000000000a72c00007604000000000000b12c00007704000000000000bb2c00007804000000000000c52c00007904000000000000cf2c00007a04000000000000d92c00007b04000000000000e32c00007c04000000000000ed2c00007d04000000000000f72c00007e04000000000000012d00007f040000000000000b2d00008004000000000000152d000081040000000000001f2d00008204000000000000292d00008304000000000000332d000084040000000000003d2d00008504000000000000472d00008604000000000000512d000087040000000000005b2d00008804000000000000652d000089040000000000006f2d00008a04000000000000792d00008b04000000000000832d00008c040000000000008d2d00008d04000000000000972d00008e04000000000000a12d00008f04000000000000ab2d00009004000000000000b52d00009104000000000000bf2d00009204000000000000c92d00009304000000000000d32d00009404000000000000dd2d00009504000000000000e72d00009604000000000000f12d00009704000000000000fb2d00009804000000000000052e000099040000000000000f2e00009a04000000000000192e00009b04000000000000232e00009c040000000000002d2e00009d04000000000000372e00009e04000000000000412e00009f040000000000004b2e0000a004000000000000552e0000a1040000000000005f2e0000a204000000000000692e0000a304000000000000732e0000a4040000000000007d2e0000a504000000000000872e0000a604000000000000912e0000a7040000000000009b2e0000a804000000000000a52e0000a904000000000000af2e0000aa04000000000000b92e0000ab04000000000000c32e0000ac04000000000000cd2e0000ad04000000000000d72e0000ae04000000000000e12e0000af04000000000000eb2e0000b004000000000000f52e0000b104000000000000ff2e0000b204000000000000092f0000b304000000000000132f0000b4040000000000001d2f0000b504000000000000272f0000b604000000000000312f0000b7040000000000003b2f0000b804000000000000452f0000b9040000000000004f2f0000ba04000000000000592f0000bb04000000000000632f0000bc040000000000006d2f0000bd04000000000000772f0000be04000000000000812f0000bf040000000000008b2f0000c004000000000000952f0000c1040000000000009f2f0000c204000000000000a92f0000c304000000000000b32f0000c404000000000000bd2f0000c504000000000000c72f0000c604000000000000d12f0000c704000000000000db2f0000c804000000000000e52f0000c904000000000000ef2f0000ca04000000000000f92f0000cb0400000000000003300000cc040000000000000d300000cd0400000000000017300000ce0400000000000021300000cf040000000000002b300000d00400000000000035300000d1040000000000003f300000d20400000000000049300000d30400000000000053300000d4040000000000005d300000d50400000000000067300000d60400000000000071300000d7040000000000007b300000d80400000000000085300000d9040000000000008f300000da0400000000000099300000db04000000000000a3300000dc04000000000000ad300000dd04000000000000b7300000de04000000000000c1300000df04000000000000cb300000e004000000000000d5300000e104000000000000df300000e204000000000000e9300000e304000000000000f3300000e404000000000000fd300000e50400000000000007310000e60400000000000011310000e7040000000000001b310000e80400000000000025310000e9040000000000002f310000ea0400000000000039310000eb0400000000000043310000ec040000000000004d310000ed0400000000000057310000ee0400000000000061310000ef040000000000006b310000f00400000000000075310000f1040000000000007f310000f20400000000000089310000f30400000000000093310000f4040000000000009d310000f504000000000000a7310000f604000000000000b1310000f704000000000000bb310000f804000000000000c5310000f904000000000000cf310000fa04000000000000d9310000fb04000000000000e3310000fc04000000000000ed310000fd04000000000000f7310000fe0400000000000001320000ff040000000000000b32000000050000000000001532000001050000000000001f32000002050000000000002932000003050000000000003332000004050000000000003d32000005050000000000004732000006050000000000005132000007050000000000005b32000008050000000000006532000009050000000000006f3200000a05000000000000793200000b05000000000000833200000c050000000000008d3200000d05000000000000973200000e05000000000000a13200000f05000000000000ab3200001005000000000000b53200001105000000000000bf3200001205000000000000c93200001305000000000000d33200001405000000000000dd3200001505000000000000e73200001605000000000000f13200001705000000000000fb32000018050000000000000533000019050000000000000f3300001a05000000000000193300001b05000000000000233300001c050000000000002d3300001d05000000000000373300001e05000000000000413300001f050000000000004b33000020050000000000005533000021050000000000005f33000022050000000000006933000023050000000000007333000024050000000000007d33000025050000000000008733000026050000000000009133000027050000000000009b3300002805000000000000a53300002905000000000000af3300002a05000000000000b93300002b05000000000000c33300002c05000000000000cd3300002d05000000000000d73300002e05000000000000e13300002f05000000000000eb3300003005000000000000f53300003105000000000000ff33000032050000000000000934000033050000000000001334000034050000000000001d34000035050000000000002734000036050000000000003134000037050000000000003b34000038050000000000004534000039050000000000004f3400003a05000000000000593400003b05000000000000633400003c050000000000006d3400003d05000000000000773400003e05000000000000813400003f050000000000008b34000040050000000000009534000041050000000000009f3400004205000000000000a93400004305000000000000b33400004405000000000000bd3400004505000000000000c73400004605000000000000d13400004705000000000000db3400004805000000000000e53400004905000000000000ef3400004a05000000000000f93400004b05000000000000033500004c050000000000000d3500004d05000000000000173500004e05000000000000213500004f050000000000002b35000050050000000000003535000051050000000000003f35000052050000000000004935000053050000000000005335000054050000000000005d35000055050000000000006735000056050000000000007135000057050000000000007b35000058050000000000008535000059050000000000008f3500005a05000000000000993500005b05000000000000a33500005c05000000000000ad3500005d05000000000000b73500005e05000000000000c13500005f05000000000000cb3500006005000000000000d53500006105000000000000df3500006205000000000000e93500006305000000000000f33500006405000000000000fd35000065050000000000000736000066050000000000001136000067050000000000001b36000068050000000000002536000069050000000000002f3600006a05000000000000393600006b05000000000000433600006c050000000000004d3600006d05000000000000573600006e05000000000000613600006f050000000000006b36000070050000000000007536000071050000000000007f36000072050000000000008936000073050000000000009336000074050000000000009d3600007505000000000000a73600007605000000000000b13600007705000000000000bb3600007805000000000000c53600007905000000000000cf3600007a05000000000000d93600007b05000000000000e33600007c05000000000000ed3600007d05000000000000f73600007e05000000000000013700007f050000000000000b37000080050000000000001537000081050000000000001f37000082050000000000002937000083050000000000003337000084050000000000003d37000085050000000000004737000086050000000000005137000087050000000000005b37000088050000000000006537000089050000000000006f3700008a05000000000000793700008b05000000000000833700008c050000000000008d3700008d05000000000000973700008e05000000000000a13700008f05000000000000ab3700009005000000000000b53700009105000000000000bf3700009205000000000000c93700009305000000000000d33700009405000000000000dd3700009505000000000000e73700009605000000000000f13700009705000000000000fb37000098050000000000000538000099050000000000000f3800009a05000000000000193800009b05000000000000233800009c050000000000002d3800009d05000000000000373800009e05000000000000413800009f050000000000004b380000a00500000000000055380000a1050000000000005f380000a20500000000000069380000a30500000000000073380000a4050000000000007d380000a50500000000000087380000a60500000000000091380000a7050000000000009b380000a805000000000000a5380000a905000000000000af380000aa05000000000000b9380000ab05000000000000c3380000ac05000000000000cd380000ad05000000000000d7380000ae05000000000000e1380000af05000000000000eb380000b005000000000000f5380000b105000000000000ff380000b20500000000000009390000b30500000000000013390000b4050000000000001d390000b50500000000000027390000b60500000000000031390000b7050000000000003b390000b80500000000000045390000b9050000000000004f390000ba0500000000000059390000bb0500000000000063390000bc050000000000006d390000bd0500000000000077390000be0500000000000081390000bf050000000000008b390000c00500000000000095390000c1050000000000009f390000c205000000000000a9390000c305000000000000b3390000c405000000000000bd390000c505000000000000c7390000c605000000000000d1390000c705000000000000db390000c805000000000000e5390000c905000000000000ef390000ca05000000000000f9390000cb05000000000000033a0000cc050000000000000d3a0000cd05000000000000173a0000ce05000000000000213a0000cf050000000000002b3a0000d005000000000000353a0000d1050000000000003f3a0000d205000000000000493a0000d305000000000000533a0000d4050000000000005d3a0000d505000000000000673a0000d605000000000000713a0000d7050000000000007b3a0000d805000000000000853a0000d9050000000000008f3a0000da05000000000000993a0000db05000000000000a33a0000dc05000000000000ad3a0000dd05000000000000b73a0000de05000000000000c13a0000df05000000000000cb3a0000e005000000000000d53a0000e105000000000000df3a0000e205000000000000e93a0000e305000000000000f33a0000e405000000000000fd3a0000e505000000000000073b0000e605000000000000113b0000e7050000000000001b3b0000e805000000000000253b0000e9050000000000002f3b0000ea05000000000000393b0000eb05000000000000433b0000ec050000000000004d3b0000ed05000000000000573b0000ee05000000000000613b0000ef050000000000006b3b0000f005000000000000753b0000f1050000000000007f3b0000f205000000000000893b0000f305000000000000933b0000f4050000000000009d3b0000f505000000000000a73b0000f605000000000000b13b0000f705000000000000bb3b0000f805000000000000c53b0000f905000000000000cf3b0000fa05000000000000d93b0000fb05000000000000e33b0000fc05000000000000ed3b0000fd05000000000000f73b0000fe05000000000000013c0000ff050000000000000b3c00000006000000000000153c000001060000000000001f3c00000206000000000000293c00000306000000000000333c000004060000000000003d3c00000506000000000000473c00000606000000000000513c000007060000000000005b3c00000806000000000000653c000009060000000000006f3c00000a06000000000000793c00000b06000000000000833c00000c060000000000008d3c00000d06000000000000973c00000e06000000000000a13c00000f06000000000000ab3c00001006000000000000b53c00001106000000000000bf3c00001206000000000000c93c00001306000000000000d33c00001406000000000000dd3c00001506000000000000e73c00001606000000000000f13c00001706000000000000fb3c00001806000000000000053d000019060000000000000f3d00001a06000000000000193d00001b06000000000000233d00001c060000000000002d3d00001d06000000000000373d00001e06000000000000413d00001f060000000000004b3d00002006000000000000553d000021060000000000005f3d00002206000000000000693d00002306000000000000733d000024060000000000007d3d00002506000000000000873d00002606000000000000913d000027060000000000009b3d00002806000000000000a53d00002906000000000000af3d00002a06000000000000b93d00002b06000000000000c33d00002c06000000000000cd3d00002d06000000000000d73d00002e06000000000000e13d00002f06000000000000eb3d00003006000000000000f53d00003106000000000000ff3d00003206000000000000093e00003306000000000000133e000034060000000000001d3e00003506000000000000273e00003606000000000000313e000037060000000000003b3e00003806000000000000453e000039060000000000004f3e00003a06000000000000593e00003b06000000000000633e00003c060000000000006d3e00003d06000000000000773e00003e06000000000000813e00003f060000000000008b3e00004006000000000000953e000041060000000000009f3e00004206000000000000a93e00004306000000000000b33e00004406000000000000bd3e00004506000000000000c73e00004606000000000000d13e00004706000000000000db3e00004806000000000000e53e00004906000000000000ef3e00004a06000000000000f93e00004b06000000000000033f00004c060000000000000d3f00004d06000000000000173f00004e06000000000000213f00004f060000000000002b3f00005006000000000000353f000051060000000000003f3f00005206000000000000493f00005306000000000000533f000054060000000000005d3f00005506000000000000673f00005606000000000000713f000057060000000000007b3f00005806000000000000853f000059060000000000008f3f00005a06000000000000993f00005b06000000000000a33f00005c06000000000000ad3f00005d06000000000000b73f00005e06000000000000c13f00005f06000000000000cb3f00006006000000000000d53f00006106000000000000df3f00006206000000000000e93f00006306000000000000f33f00006406000000000000fd3f000065060000000000000740000066060000000000001140000067060000000000001b40000068060000000000002540000069060000000000002f4000006a06000000000000394000006b06000000000000434000006c060000000000004d4000006d06000000000000574000006e06000000000000614000006f060000000000006b40000070060000000000007540000071060000000000007f40000072060000000000008940000073060000000000009340000074060000000000009d4000007506000000000000a74000007606000000000000b14000007706000000000000bb4000007806000000000000c54000007906000000000000cf4000007a06000000000000d94000007b06000000000000e34000007c06000000000000ed4000007d06000000000000f74000007e06000000000000014100007f060000000000000b41000080060000000000001541000081060000000000001f41000082060000000000002941000083060000000000003341000084060000000000003d41000085060000000000004741000086060000000000005141000087060000000000005b41000088060000000000006541000089060000000000006f4100008a06000000000000794100008b06000000000000834100008c060000000000008d4100008d06000000000000974100008e06000000000000a14100008f06000000000000ab4100009006000000000000b54100009106000000000000bf4100009206000000000000c94100009306000000000000d34100009406000000000000dd4100009506000000000000e74100009606000000000000f14100009706000000000000fb41000098060000000000000542000099060000000000000f4200009a06000000000000194200009b06000000000000234200009c060000000000002d4200009d06000000000000374200009e06000000000000414200009f060000000000004b420000a00600000000000055420000a1060000000000005f420000a20600000000000069420000a30600000000000073420000a4060000000000007d420000a50600000000000087420000a60600000000000091420000a7060000000000009b420000a806000000000000a5420000a906000000000000af420000aa06000000000000b9420000ab06000000000000c3420000ac06000000000000cd420000ad06000000000000d7420000ae06000000000000e1420000af06000000000000eb420000b006000000000000f5420000b106000000000000ff420000b20600000000000009430000b30600000000000013430000b4060000000000001d430000b50600000000000027430000b60600000000000031430000b7060000000000003b430000b80600000000000045430000b9060000000000004f430000ba0600000000000059430000bb0600000000000063430000bc060000000000006d430000bd0600000000000077430000be0600000000000081430000bf060000000000008b430000c00600000000000095430000c1060000000000009f430000c206000000000000a9430000c306000000000000b3430000c406000000000000bd430000c506000000000000c7430000c606000000000000d1430000c706000000000000db430000c806000000000000e5430000c906000000000000ef430000ca06000000000000f9430000cb0600000000000003440000cc060000000000000d440000cd0600000000000017440000ce0600000000000021440000cf060000000000002b440000d00600000000000035440000d1060000000000003f440000d20600000000000049440000d30600000000000053440000d4060000000000005d440000d50600000000000067440000d60600000000000071440000d7060000000000007b440000d80600000000000085440000d9060000000000008f440000da0600000000000099440000db06000000000000a3440000dc06000000000000ad440000dd06000000000000b7440000de06000000000000c1440000df06000000000000cb440000e006000000000000d5440000e106000000000000df440000e206000000000000e9440000e306000000000000f3440000e406000000000000fd440000e50600000000000007450000e60600000000000011450000e7060000000000001b450000e80600000000000025450000e9060000000000002f450000ea0600000000000039450000eb0600000000000043450000ec060000000000004d450000ed0600000000000057450000ee0600000000000061450000ef060000000000006b450000f00600000000000075450000f1060000000000007f450000f20600000000000089450000f30600000000000093450000f4060000000000009d450000f506000000000000a7450000f606000000000000b1450000f706000000000000bb450000f806000000000000c5450000f906000000000000cf450000fa06000000000000d9450000fb06000000000000e3450000fc06000000000000ed450000fd06000000000000f7450000fe0600000000000001460000ff060000000000000b46000000070000000000001546000001070000000000001f46000002070000000000002946000003070000000000003346000004070000000000003d46000005070000000000004746000006070000000000005146000007070000000000005b46000008070000000000006546000009070000000000006f4600000a07000000000000794600000b07000000000000834600000c070000000000008d4600000d07000000000000974600000e07000000000000a14600000f07000000000000ab4600001007000000000000b54600001107000000000000bf4600001207000000000000c94600001307000000000000d34600001407000000000000dd4600001507000000000000e74600001607000000000000f14600001707000000000000fb46000018070000000000000547000019070000000000000f4700001a07000000000000194700001b07000000000000234700001c070000000000002d4700001d07000000000000374700001e07000000000000414700001f070000000000004b47000020070000000000005547000021070000000000005f47000022070000000000006947000023070000000000007347000024070000000000007d47000025070000000000008747000026070000000000009147000027070000000000009b4700002807000000000000a54700002907000000000000af4700002a07000000000000b94700002b07000000000000c34700002c07000000000000cd4700002d07000000000000d74700002e07000000000000e14700002f07000000000000eb4700003007000000000000f54700003107000000000000ff47000032070000000000000948000033070000000000001348000034070000000000001d48000035070000000000002748000036070000000000003148000037070000000000003b48000038070000000000004548000039070000000000004f4800003a07000000000000594800003b07000000000000634800003c070000000000006d4800003d07000000000000774800003e07000000000000814800003f070000000000008b48000040070000000000009548000041070000000000009f4800004207000000000000a94800004307000000000000b34800004407000000000000bd4800004507000000000000c74800004607000000000000d14800004707000000000000db4800004807000000000000e54800004907000000000000ef4800004a07000000000000f94800004b07000000000000034900004c070000000000000d4900004d07000000000000174900004e07000000000000214900004f070000000000002b49000050070000000000003549000051070000000000003f49000052070000000000004949000053070000000000005349000054070000000000005d49000055070000000000006749000056070000000000007149000057070000000000007b49000058070000000000008549000059070000000000008f4900005a07000000000000994900005b07000000000000a34900005c07000000000000ad4900005d07000000000000b74900005e07000000000000c14900005f07000000000000cb4900006007000000000000d54900006107000000000000df4900006207000000000000e94900006307000000000000f34900006407000000000000fd4900006507000000000000074a00006607000000000000114a000067070000000000001b4a00006807000000000000254a000069070000000000002f4a00006a07000000000000394a00006b07000000000000434a00006c070000000000004d4a00006d07000000000000574a00006e07000000000000614a00006f070000000000006b4a00007007000000000000754a000071070000000000007f4a00007207000000000000894a00007307000000000000934a000074070000000000009d4a00007507000000000000a74a00007607000000000000b14a00007707000000000000bb4a00007807000000000000c54a00007907000000000000cf4a00007a07000000000000d94a00007b07000000000000e34a00007c07000000000000ed4a00007d07000000000000f74a00007e07000000000000014b00007f070000000000000b4b00008007000000000000154b000081070000000000001f4b00008207000000000000294b00008307000000000000334b000084070000000000003d4b00008507000000000000474b00008607000000000000514b000087070000000000005b4b00008807000000000000654b000089070000000000006f4b00008a07000000000000794b00008b07000000000000834b00008c070000000000008d4b00008d07000000000000974b00008e07000000000000a14b00008f07000000000000ab4b00009007000000000000b54b00009107000000000000bf4b00009207000000000000c94b00009307000000000000d34b00009407000000000000dd4b00009507000000000000e74b00009607000000000000f14b00009707000000000000fb4b00009807000000000000054c000099070000000000000f4c00009a07000000000000194c00009b07000000000000234c00009c070000000000002d4c00009d07000000000000374c00009e07000000000000414c00009f070000000000004b4c0000a007000000000000554c0000a1070000000000005f4c0000a207000000000000694c0000a307000000000000734c0000a4070000000000007d4c0000a507000000000000874c0000a607000000000000914c0000a7070000000000009b4c0000a807000000000000a54c0000a907000000000000af4c0000aa07000000000000b94c0000ab07000000000000c34c0000ac07000000000000cd4c0000ad07000000000000d74c0000ae07000000000000e14c0000af07000000000000eb4c0000b007000000000000f54c0000b107000000000000ff4c0000b207000000000000094d0000b307000000000000134d0000b4070000000000001d4d0000b507000000000000274d0000b607000000000000314d0000b7070000000000003b4d0000b807000000000000454d0000b9070000000000004f4d0000ba07000000000000594d0000bb07000000000000634d0000bc070000000000006d4d0000bd07000000000000774d0000be07000000000000814d0000bf070000000000008b4d0000c007000000000000954d0000c1070000000000009f4d0000c207000000000000a94d0000c307000000000000b34d0000c407000000000000bd4d0000c507000000000000c74d0000c607000000000000d14d0000c707000000000000db4d0000c807000000000000e54d0000c907000000000000ef4d0000ca07000000000000f94d0000cb07000000000000034e0000cc070000000000000d4e0000cd07000000000000174e0000ce07000000000000214e0000cf070000000000002b4e0000d007000000000000354e0000d1070000000000003f4e0000d207000000000000494e0000d307000000000000534e0000d4070000000000005d4e0000d507000000000000674e0000d607000000000000714e0000d7070000000000007b4e0000d807000000000000854e0000d9070000000000008f4e0000da07000000000000994e0000db07000000000000a34e0000dc07000000000000ad4e0000dd07000000000000b74e0000de07000000000000c14e0000df07000000000000cb4e0000e007000000000000d54e0000e107000000000000df4e0000e207000000000000e94e0000e307000000000000f34e0000e407000000000000fd4e0000e507000000000000074f0000e607000000000000114f0000e7070000000000001b4f0000e807000000000000254f0000e9070000000000002f4f0000ea07000000000000394f0000eb07000000000000434f0000ec070000000000004d4f0000ed07000000000000574f0000ee07000000000000614f0000ef070000000000006b4f0000f007000000000000754f0000f1070000000000007f4f0000f207000000000000894f0000f307000000000000934f0000f4070000000000009d4f0000f507000000000000a74f0000f607000000000000b14f0000f707000000000000bb4f0000f807000000000000c54f0000f907000000000000cf4f0000fa07000000000000d94f0000fb07000000000000e34f0000fc07000000000000ed4f0000fd07000000000000f74f0000fe0700000000000001500000ff070000000000000b50000000080000000000001550000001080000000000001f50000002080000000000002950000003080000000000003350000004080000000000003d50000005080000000000004750000006080000000000005150000007080000000000005b50000008080000000000006550000009080000000000006f5000000a08000000000000795000000b08000000000000835000000c080000000000008d5000000d08000000000000975000000e08000000000000a15000000f08000000000000ab5000001008000000000000b55000001108000000000000bf5000001208000000000000c95000001308000000000000d35000001408000000000000dd5000001508000000000000e75000001608000000000000f15000001708000000000000fb50000018080000000000000551000019080000000000000f5100001a08000000000000195100001b08000000000000235100001c080000000000002d5100001d08000000000000375100001e08000000000000415100001f080000000000004b51000020080000000000005551000021080000000000005f51000022080000000000006951000023080000000000007351000024080000000000007d51000025080000000000008751000026080000000000009151000027080000000000009b5100002808000000000000a55100002908000000000000af5100002a08000000000000b95100002b08000000000000c35100002c08000000000000cd5100002d08000000000000d75100002e08000000000000e15100002f08000000000000eb5100003008000000000000f55100003108000000000000ff51000032080000000000000952000033080000000000001352000034080000000000001d52000035080000000000002752000036080000000000003152000037080000000000003b52000038080000000000004552000039080000000000004f5200003a08000000000000595200003b08000000000000635200003c080000000000006d5200003d08000000000000775200003e08000000000000815200003f080000000000008b52000040080000000000009552000041080000000000009f5200004208000000000000a95200004308000000000000b35200004408000000000000bd5200004508000000000000c75200004608000000000000d15200004708000000000000db5200004808000000000000e55200004908000000000000ef5200004a08000000000000f95200004b08000000000000035300004c080000000000000d5300004d08000000000000175300004e08000000000000215300004f080000000000002b53000050080000000000003553000051080000000000003f53000052080000000000004953000053080000000000005353000054080000000000005d53000055080000000000006753000056080000000000007153000057080000000000007b53000058080000000000008553000059080000000000008f5300005a08000000000000995300005b08000000000000a35300005c08000000000000ad5300005d08000000000000b75300005e08000000000000c15300005f08000000000000cb5300006008000000000000d55300006108000000000000df5300006208000000000000e95300006308000000000000f35300006408000000000000fd53000065080000000000000754000066080000000000001154000067080000000000001b54000068080000000000002554000069080000000000002f5400006a08000000000000395400006b08000000000000435400006c080000000000004d5400006d08000000000000575400006e08000000000000615400006f080000000000006b54000070080000000000007554000071080000000000007f54000072080000000000008954000073080000000000009354000074080000000000009d5400007508000000000000a75400007608000000000000b15400007708000000000000bb5400007808000000000000c55400007908000000000000cf5400007a08000000000000d95400007b08000000000000e35400007c08000000000000ed5400007d08000000000000f75400007e08000000000000015500007f080000000000000b55000080080000000000001555000081080000000000001f55000082080000000000002955000083080000000000003355000084080000000000003d55000085080000000000004755000086080000000000005155000087080000000000005b55000088080000000000006555000089080000000000006f5500008a08000000000000795500008b08000000000000835500008c080000000000008d5500008d08000000000000975500008e08000000000000a15500008f08000000000000ab5500009008000000000000b55500009108000000000000bf5500009208000000000000c95500009308000000000000d35500009408000000000000dd5500009508000000000000e75500009608000000000000f15500009708000000000000fb55000098080000000000000556000099080000000000000f5600009a08000000000000195600009b08000000000000235600009c080000000000002d5600009d08000000000000375600009e08000000000000415600009f080000000000004b560000a00800000000000055560000a1080000000000005f560000a20800000000000069560000a30800000000000073560000a4080000000000007d560000a50800000000000087560000a60800000000000091560000a7080000000000009b560000a808000000000000a5560000a908000000000000af560000aa08000000000000b9560000ab08000000000000c3560000ac08000000000000cd560000ad08000000000000d7560000ae08000000000000e1560000af08000000000000eb560000b008000000000000f5560000b108000000000000ff560000b20800000000000009570000b30800000000000013570000b4080000000000001d570000b50800000000000027570000b60800000000000031570000b7080000000000003b570000b80800000000000045570000b9080000000000004f570000ba0800000000000059570000bb0800000000000063570000bc080000000000006d570000bd0800000000000077570000be0800000000000081570000bf080000000000008b570000c00800000000000095570000c1080000000000009f570000c208000000000000a9570000c308000000000000b3570000c408000000000000bd570000c508000000000000c7570000c608000000000000d1570000c708000000000000db570000c808000000000000e5570000c908000000000000ef570000ca08000000000000f9570000cb0800000000000003580000cc080000000000000d580000cd0800000000000017580000ce0800000000000021580000cf080000000000002b580000d00800000000000035580000d1080000000000003f580000d20800000000000049580000d30800000000000053580000d4080000000000005d580000d50800000000000067580000d60800000000000071580000d7080000000000007b580000d80800000000000085580000d9080000000000008f580000da0800000000000099580000db08000000000000a3580000dc08000000000000ad580000dd08000000000000b7580000de08000000000000c1580000df08000000000000cb580000e008000000000000d5580000e108000000000000df580000e208000000000000e9580000e308000000000000f3580000e408000000000000fd580000e50800000000000007590000e60800000000000011590000e7080000000000001b590000e80800000000000025590000e9080000000000002f590000ea0800000000000039590000eb0800000000000043590000ec080000000000004d590000ed0800000000000057590000ee0800000000000061590000ef080000000000006b590000f00800000000000075590000f1080000000000007f590000f20800000000000089590000f30800000000000093590000f4080000000000009d590000f508000000000000a7590000f608000000000000b1590000f708000000000000bb590000f808000000000000c5590000f908000000000000cf590000fa08000000000000d9590000fb08000000000000e3590000fc08000000000000ed590000fd08000000000000f7590000fe08000000000000015a0000ff080000000000000b5a00000009000000000000155a000001090000000000001f5a00000209000000000000295a00000309000000000000335a000004090000000000003d5a00000509000000000000475a00000609000000000000515a000007090000000000005b5a00000809000000000000655a000009090000000000006f5a00000a09000000000000795a00000b09000000000000835a00000c090000000000008d5a00000d09000000000000975a00000e09000000000000a15a00000f09000000000000ab5a00001009000000000000b55a00001109000000000000bf5a00001209000000000000c95a00001309000000000000d35a00001409000000000000dd5a00001509000000000000e75a00001609000000000000f15a00001709000000000000fb5a00001809000000000000055b000019090000000000000f5b00001a09000000000000195b00001b09000000000000235b00001c090000000000002d5b00001d09000000000000375b00001e09000000000000415b00001f090000000000004b5b00002009000000000000555b000021090000000000005f5b00002209000000000000695b00002309000000000000735b000024090000000000007d5b00002509000000000000875b00002609000000000000915b000027090000000000009b5b00002809000000000000a55b00002909000000000000af5b00002a09000000000000b95b00002b09000000000000c35b00002c09000000000000cd5b00002d09000000000000d75b00002e09000000000000e15b00002f09000000000000eb5b00003009000000000000f55b00003109000000000000ff5b00003209000000000000095c00003309000000000000135c000034090000000000001d5c00003509000000000000275c00003609000000000000315c000037090000000000003b5c00003809000000000000455c000039090000000000004f5c00003a09000000000000595c00003b09000000000000635c00003c090000000000006d5c00003d09000000000000775c00003e09000000000000815c00003f090000000000008b5c00004009000000000000955c000041090000000000009f5c00004209000000000000a95c00004309000000000000b35c00004409000000000000bd5c00004509000000000000c75c00004609000000000000d15c00004709000000000000db5c00004809000000000000e55c00004909000000000000ef5c00004a09000000000000f95c00004b09000000000000035d00004c090000000000000d5d00004d09000000000000175d00004e09000000000000215d00004f090000000000002b5d00005009000000000000355d000051090000000000003f5d00005209000000000000495d00005309000000000000535d000054090000000000005d5d00005509000000000000675d00005609000000000000715d000057090000000000007b5d00005809000000000000855d000059090000000000008f5d00005a09000000000000995d00005b09000000000000a35d00005c09000000000000ad5d00005d09000000000000b75d00005e09000000000000c15d00005f09000000000000cb5d00006009000000000000d55d00006109000000000000df5d00006209000000000000e95d00006309000000000000f35d00006409000000000000fd5d00006509000000000000075e00006609000000000000115e000067090000000000001b5e00006809000000000000255e000069090000000000002f5e00006a09000000000000395e00006b09000000000000435e00006c090000000000004d5e00006d09000000000000575e00006e09000000000000615e00006f090000000000006b5e00007009000000000000755e000071090000000000007f5e00007209000000000000895e00007309000000000000935e000074090000000000009d5e00007509000000000000a75e00007609000000000000b15e00007709000000000000bb5e00007809000000000000c55e00007909000000000000cf5e00007a09000000000000d95e00007b09000000000000e35e00007c09000000000000ed5e00007d09000000000000f75e00007e09000000000000015f00007f090000000000000b5f00008009000000000000155f000081090000000000001f5f00008209000000000000295f00008309000000000000335f000084090000000000003d5f00008509000000000000475f00008609000000000000515f000087090000000000005b5f00008809000000000000655f000089090000000000006f5f00008a09000000000000795f00008b09000000000000835f00008c090000000000008d5f00008d09000000000000975f00008e09000000000000a15f00008f09000000000000ab5f00009009000000000000b55f00009109000000000000bf5f00009209000000000000c95f00009309000000000000d35f00009409000000000000dd5f00009509000000000000e75f00009609000000000000f15f00009709000000000000fb5f000098090000000000000560000099090000000000000f6000009a09000000000000196000009b09000000000000236000009c090000000000002d6000009d09000000000000376000009e09000000000000416000009f090000000000004b600000a00900000000000055600000a1090000000000005f600000a20900000000000069600000a30900000000000073600000a4090000000000007d600000a50900000000000087600000a60900000000000091600000a7090000000000009b600000a809000000000000a5600000a909000000000000af600000aa09000000000000b9600000ab09000000000000c3600000ac09000000000000cd600000ad09000000000000d7600000ae09000000000000e1600000af09000000000000eb600000b009000000000000f5600000b109000000000000ff600000b20900000000000009610000b30900000000000013610000b4090000000000001d610000b50900000000000027610000b60900000000000031610000b7090000000000003b610000b80900000000000045610000b9090000000000004f610000ba0900000000000059610000bb0900000000000063610000bc090000000000006d610000bd0900000000000077610000be0900000000000081610000bf090000000000008b610000c00900000000000095610000c1090000000000009f610000c209000000000000a9610000c309000000000000b3610000c409000000000000bd610000c509000000000000c7610000c609000000000000d1610000c709000000000000db610000c809000000000000e5610000c909000000000000ef610000ca09000000000000f9610000cb0900000000000003620000cc090000000000000d620000cd0900000000000017620000ce0900000000000021620000cf090000000000002b620000d00900000000000035620000d1090000000000003f620000d20900000000000049620000d30900000000000053620000d4090000000000005d620000d50900000000000067620000d60900000000000071620000d7090000000000007b620000d80900000000000085620000d9090000000000008f620000da0900000000000099620000db09000000000000a3620000dc09000000000000ad620000dd09000000000000b7620000de09000000000000c1620000df09000000000000cb620000e009000000000000d5620000e109000000000000df620000e209000000000000e9620000e309000000000000f3620000e409000000000000fd620000e50900000000000007630000e60900000000000011630000e7090000000000001b630000e80900000000000025630000e9090000000000002f630000ea0900000000000039630000eb0900000000000043630000ec090000000000004d630000ed0900000000000057630000ee0900000000000061630000ef090000000000006b630000f00900000000000075630000f1090000000000007f630000f20900000000000089630000f30900000000000093630000f4090000000000009d630000f509000000000000a7630000f609000000000000b1630000f709000000000000bb630000f809000000000000c5630000f909000000000000cf630000fa09000000000000d9630000fb09000000000000e3630000fc09000000000000ed630000fd09000000000000f7630000fe0900000000000001640000ff090000000000000b640000000a00000000000015640000010a0000000000001f640000020a00000000000029640000030a00000000000033640000040a0000000000003d640000050a00000000000047640000060a00000000000051640000070a0000000000005b640000080a00000000000065640000090a0000000000006f6400000a0a000000000000796400000b0a000000000000836400000c0a0000000000008d6400000d0a000000000000976400000e0a000000000000a16400000f0a000000000000ab640000100a000000000000b5640000110a000000000000bf640000120a000000000000c9640000130a000000000000d3640000140a000000000000dd640000150a000000000000e7640000160a000000000000f1640000170a000000000000fb640000180a00000000000005650000190a0000000000000f6500001a0a000000000000196500001b0a000000000000236500001c0a0000000000002d6500001d0a000000000000376500001e0a000000000000416500001f0a0000000000004b650000200a00000000000055650000210a0000000000005f650000220a00000000000069650000230a00000000000073650000240a0000000000007d650000250a00000000000087650000260a00000000000091650000270a0000000000009b650000280a000000000000a5650000290a000000000000af6500002a0a000000000000b96500002b0a000000000000c36500002c0a000000000000cd6500002d0a000000000000d76500002e0a000000000000e16500002f0a000000000000eb650000300a000000000000f5650000310a000000000000ff650000320a00000000000009660000330a00000000000013660000340a0000000000001d660000350a00000000000027660000360a00000000000031660000370a0000000000003b660000380a00000000000045660000390a0000000000004f6600003a0a000000000000596600003b0a000000000000636600003c0a0000000000006d6600003d0a000000000000776600003e0a000000000000816600003f0a0000000000008b660000400a00000000000095660000410a0000000000009f660000420a000000000000a9660000430a000000000000b3660000440a000000000000bd660000450a000000000000c7660000460a000000000000d1660000470a000000000000db660000480a000000000000e5660000490a000000000000ef6600004a0a000000000000f96600004b0a000000000000036700004c0a0000000000000d6700004d0a000000000000176700004e0a000000000000216700004f0a0000000000002b670000500a00000000000035670000510a0000000000003f670000520a00000000000049670000530a00000000000053670000540a0000000000005d670000550a00000000000067670000560a00000000000071670000570a0000000000007b670000580a00000000000085670000590a0000000000008f6700005a0a000000000000996700005b0a000000000000a36700005c0a000000000000ad6700005d0a000000000000b76700005e0a000000000000c16700005f0a000000000000cb670000600a000000000000cc670000610a000000000000d5670000620a000000000000df670000630a000000000000e9670000640a000000000000f3670000650a000000000000fd670000660a00000000000007680000670a00000000000011680000680a0000000000001b680000690a000000000000256800006a0a0000000000002f6800006b0a000000000000396800006c0a000000000000436800006d0a0000000000004d6800006e0a000000000000576800006f0a00000000000061680000700a0000000000006b680000710a00000000000075680000720a0000000000007f680000730a00000000000089680000740a00000000000093680000750a0000000000009d680000760a000000000000a7680000770a000000000000b1680000780a000000000000bb680000790a000000000000c56800007a0a000000000000cf6800007b0a000000000000d96800007c0a000000000000e36800007d0a000000000000ed6800007e0a000000000000f76800007f0a00000000000001690000800a0000000000000b690000810a00000000000015690000820a0000000000001f690000830a00000000000029690000840a00000000000033690000850a0000000000003d690000860a00000000000047690000870a00000000000051690000880a0000000000005b690000890a000000000000656900008a0a0000000000006f6900008b0a000000000000796900008c0a000000000000836900008d0a0000000000008d6900008e0a000000000000976900008f0a000000000000a1690000900a000000000000ab690000910a000000000000b5690000920a000000000000bf690000930a000000000000c9690000940a000000000000d3690000950a000000000000dd690000960a000000000000e7690000970a000000000000f1690000980a000000000000fb690000990a000000000000056a00009a0a0000000000000f6a00009b0a000000000000196a00009c0a000000000000236a00009d0a0000000000002d6a00009e0a000000000000376a00009f0a000000000000416a0000a00a0000000000004b6a0000a10a000000000000556a0000a20a0000000000005f6a0000a30a000000000000696a0000a40a000000000000736a0000a50a0000000000007d6a0000a60a000000000000876a0000a70a000000000000916a0000a80a0000000000009b6a0000a90a000000000000a56a0000aa0a000000000000af6a0000ab0a000000000000b96a0000ac0a000000000000c36a0000ad0a000000000000cd6a0000ae0a000000000000d76a0000af0a000000000000e16a0000b00a000000000000eb6a0000b10a000000000000f56a0000b20a000000000000ff6a0000b30a000000000000096b0000b40a000000000000136b0000b50a0000000000001d6b0000b60a000000000000276b0000b70a000000000000316b0000b80a0000000000003b6b0000b90a000000000000456b0000ba0a0000000000004f6b0000bb0a000000000000596b0000bc0a000000000000636b0000bd0a0000000000006d6b0000be0a000000000000776b0000bf0a000000000000816b0000c00a0000000000008b6b0000c10a000000000000956b0000c20a0000000000009f6b0000c30a000000000000a96b0000c40a000000000000b36b0000c50a000000000000bd6b0000c60a000000000000c76b0000c70a000000000000d16b0000c80a000000000000db6b0000c90a000000000000e56b0000ca0a000000000000ef6b0000cb0a000000000000f96b0000cc0a000000000000036c0000cd0a0000000000000d6c0000ce0a000000000000176c0000cf0a000000000000216c0000d00a0000000000002b6c0000d10a000000000000356c0000d20a0000000000003f6c0000d30a000000000000496c0000d40a000000000000536c0000d50a0000000000005d6c0000d60a000000000000676c0000d70a000000000000716c0000d80a0000000000007b6c0000d90a000000000000856c0000da0a0000000000008f6c0000db0a000000000000996c0000dc0a000000000000a36c0000dd0a000000000000ad6c0000de0a000000000000b76c0000df0a000000000000c16c0000e00a000000000000cb6c0000e10a000000000000d56c0000e20a000000000000df6c0000e30a000000000000e96c0000e40a000000000000f36c0000e50a000000000000fd6c0000e60a000000000000076d0000e70a000000000000116d0000e80a0000000000001b6d0000e90a000000000000256d0000ea0a0000000000002f6d0000eb0a000000000000396d0000ec0a000000000000436d0000ed0a0000000000004d6d0000ee0a000000000000576d0000ef0a000000000000616d0000f00a0000000000006b6d0000f10a000000000000756d0000f20a0000000000007f6d0000f30a000000000000896d0000f40a000000000000936d0000f50a0000000000009d6d0000f60a000000000000a76d0000f70a000000000000b16d0000f80a000000000000bb6d0000f90a000000000000c56d0000fa0a000000000000cf6d0000fb0a000000000000d96d0000fc0a000000000000e36d0000fd0a000000000000ed6d0000fe0a000000000000f76d0000ff0a000000000000016e0000000b0000000000000b6e0000010b000000000000156e0000020b0000000000001f6e0000030b000000000000296e0000040b000000000000336e0000050b0000000000003d6e0000060b000000000000476e0000070b000000000000516e0000080b0000000000005b6e0000090b000000000000656e00000a0b0000000000006f6e00000b0b000000000000796e00000c0b000000000000836e00000d0b0000000000008d6e00000e0b000000000000976e00000f0b000000000000a16e0000100b000000000000ab6e0000110b000000000000b56e0000120b000000000000bf6e0000130b000000000000c96e0000140b000000000000d36e0000150b000000000000dd6e0000160b000000000000e76e0000170b000000000000f16e0000180b000000000000fb6e0000190b000000000000056f00001a0b0000000000000f6f00001b0b000000000000196f00001c0b000000000000236f00001d0b0000000000002d6f00001e0b000000000000376f00001f0b000000000000416f0000200b0000000000004b6f0000210b000000000000556f0000220b0000000000005f6f0000230b000000000000696f0000240b000000000000736f0000250b0000000000007d6f0000260b000000000000876f0000270b000000000000916f0000280b0000000000009b6f0000290b000000000000a56f00002a0b000000000000af6f00002b0b000000000000b96f00002c0b000000000000c36f00002d0b000000000000cd6f00002e0b000000000000d76f00002f0b000000000000e16f0000300b000000000000eb6f0000310b000000000000f56f0000320b000000000000ff6f0000330b00000000000009700000340b00000000000013700000350b0000000000001d700000360b00000000000027700000370b00000000000031700000380b0000000000003b700000390b000000000000457000003a0b0000000000004f7000003b0b000000000000597000003c0b000000000000637000003d0b0000000000006d7000003e0b000000000000777000003f0b00000000000081700000400b0000000000008b700000410b00000000000095700000420b0000000000009f700000430b000000000000a9700000440b000000000000b3700000450b000000000000bd700000460b000000000000c7700000470b000000000000d1700000480b000000000000db700000490b000000000000e57000004a0b000000000000ef7000004b0b000000000000f97000004c0b000000000000037100004d0b0000000000000d7100004e0b000000000000177100004f0b00000000000021710000500b0000000000002b710000510b00000000000035710000520b0000000000003f710000530b00000000000049710000540b00000000000053710000550b0000000000005d710000560b00000000000067710000570b00000000000071710000580b0000000000007b710000590b000000000000857100005a0b0000000000008f7100005b0b000000000000997100005c0b000000000000a37100005d0b000000000000ad7100005e0b000000000000b77100005f0b000000000000c1710000600b000000000000cb710000610b000000000000d5710000620b000000000000df710000630b000000000000e9710000640b000000000000f3710000650b000000000000fd710000660b00000000000007720000670b00000000000011720000680b0000000000001b720000690b000000000000257200006a0b0000000000002f7200006b0b000000000000397200006c0b000000000000437200006d0b0000000000004d7200006e0b000000000000577200006f0b00000000000061720000700b0000000000006b720000710b00000000000075720000720b0000000000007f720000730b00000000000089720000740b00000000000093720000750b0000000000009d720000760b000000000000a7720000770b000000000000b1720000780b000000000000bb720000790b000000000000c57200007a0b000000000000cf7200007b0b000000000000d97200007c0b000000000000e37200007d0b000000000000ed7200007e0b000000000000f77200007f0b00000000000001730000800b0000000000000b730000810b00000000000015730000820b0000000000001f730000830b00000000000029730000840b00000000000033730000850b0000000000003d730000860b00000000000047730000870b00000000000051730000880b0000000000005b730000890b000000000000657300008a0b0000000000006f7300008b0b000000000000797300008c0b000000000000837300008d0b0000000000008d7300008e0b000000000000977300008f0b000000000000a1730000900b000000000000ab730000910b000000000000b5730000920b000000000000bf730000930b000000000000c9730000940b000000000000d3730000950b000000000000dd730000960b000000000000e7730000970b000000000000f1730000980b000000000000fb730000990b000000000000057400009a0b0000000000000f7400009b0b000000000000197400009c0b000000000000237400009d0b0000000000002d7400009e0b000000000000377400009f0b00000000000041740000a00b0000000000004b740000a10b00000000000055740000a20b0000000000005f740000a30b00000000000069740000a40b00000000000073740000a50b0000000000007d740000a60b00000000000087740000a70b00000000000091740000a80b0000000000009b740000a90b000000000000a5740000aa0b000000000000af740000ab0b000000000000b9740000ac0b000000000000c3740000ad0b000000000000cd740000ae0b000000000000d7740000af0b000000000000e1740000b00b000000000000eb740000b10b000000000000f5740000b20b000000000000ff740000b30b00000000000009750000b40b00000000000013750000b50b0000000000001d750000b60b00000000000027750000b70b00000000000031750000b80b0000000000003b750000b90b00000000000045750000ba0b0000000000004f750000bb0b00000000000059750000bc0b00000000000063750000bd0b0000000000006d750000be0b00000000000077750000bf0b00000000000081750000c00b0000000000008b750000c10b00000000000095750000c20b0000000000009f750000c30b000000000000a9750000c40b000000000000b3750000c50b000000000000bd750000c60b000000000000c7750000c70b000000000000d1750000c80b000000000000db750000c90b000000000000e5750000ca0b000000000000ef750000cb0b000000000000f9750000cc0b00000000000003760000cd0b0000000000000d760000ce0b00000000000017760000cf0b00000000000021760000d00b0000000000002b760000d10b00000000000035760000d20b0000000000003f760000d30b00000000000049760000d40b00000000000053760000d50b0000000000005d760000d60b00000000000067760000d70b00000000000071760000d80b0000000000007b760000d90b00000000000085760000da0b0000000000008f760000db0b00000000000099760000dc0b000000000000a3760000dd0b000000000000ad760000de0b000000000000b7760000df0b000000000000c1760000e00b000000000000cb760000e10b000000000000d5760000e20b000000000000df760000e30b000000000000e9760000e40b000000000000f3760000e50b000000000000fd760000e60b00000000000007770000e70b00000000000011770000e80b0000000000001b770000e90b00000000000025770000ea0b0000000000002f770000eb0b00000000000039770000ec0b00000000000043770000ed0b0000000000004d770000ee0b00000000000057770000ef0b00000000000061770000f00b0000000000006b770000f10b00000000000075770000f20b0000000000007f770000f30b00000000000089770000f40b00000000000093770000f50b0000000000009d770000f60b000000000000a7770000f70b000000000000b1770000f80b000000000000bb770000f90b000000000000c5770000fa0b000000000000cf770000fb0b000000000000d9770000fc0b000000000000e3770000fd0b000000000000ed770000fe0b000000000000f7770000ff0b00000000000001780000000c0000000000000b780000010c00000000000015780000020c0000000000001f780000030c00000000000029780000040c00000000000033780000050c0000000000003d780000060c00000000000047780000070c00000000000051780000080c0000000000005b780000090c000000000000657800000a0c0000000000006f7800000b0c000000000000797800000c0c000000000000837800000d0c0000000000008d7800000e0c000000000000977800000f0c000000000000a1780000100c000000000000ab780000110c000000000000b5780000120c000000000000bf780000130c000000000000c9780000140c000000000000d3780000150c000000000000dd780000160c000000000000e7780000170c000000000000f1780000180c000000000000fb780000190c000000000000057900001a0c0000000000000f7900001b0c000000000000197900001c0c000000000000237900001d0c0000000000002d7900001e0c000000000000377900001f0c00000000000041790000200c0000000000004b790000210c00000000000055790000220c0000000000005f790000230c00000000000069790000240c00000000000073790000250c0000000000007d790000260c00000000000087790000270c00000000000091790000280c0000000000009b790000290c000000000000a57900002a0c000000000000af7900002b0c000000000000b97900002c0c000000000000c37900002d0c000000000000cd7900002e0c000000000000d77900002f0c000000000000e1790000300c000000000000eb790000310c000000000000f5790000320c000000000000ff790000330c000000000000097a0000340c000000000000137a0000350c0000000000001d7a0000360c000000000000277a0000370c000000000000317a0000380c0000000000003b7a0000390c000000000000457a00003a0c0000000000004f7a00003b0c000000000000597a00003c0c000000000000637a00003d0c0000000000006d7a00003e0c000000000000777a00003f0c000000000000817a0000400c0000000000008b7a0000410c000000000000957a0000420c0000000000009f7a0000430c000000000000a97a0000440c000000000000b37a0000450c000000000000bd7a0000460c000000000000c77a0000470c000000000000d17a0000480c000000000000db7a0000490c000000000000e57a00004a0c000000000000ef7a00004b0c000000000000f97a00004c0c000000000000037b00004d0c0000000000000d7b00004e0c000000000000177b00004f0c000000000000217b0000500c0000000000002b7b0000510c000000000000357b0000520c0000000000003f7b0000530c000000000000497b0000540c000000000000537b0000550c0000000000005d7b0000560c000000000000677b0000570c000000000000717b0000580c0000000000007b7b0000590c000000000000857b00005a0c0000000000008f7b00005b0c000000000000997b00005c0c000000000000a37b00005d0c000000000000ad7b00005e0c000000000000b77b00005f0c000000000000c17b0000600c000000000000cb7b0000610c000000000000d57b0000620c000000000000df7b0000630c000000000000e97b0000640c000000000000f37b0000650c000000000000fd7b0000660c000000000000077c0000670c000000000000117c0000680c0000000000001b7c0000690c000000000000257c00006a0c0000000000002f7c00006b0c000000000000397c00006c0c000000000000437c00006d0c0000000000004d7c00006e0c000000000000577c00006f0c000000000000617c0000700c0000000000006b7c0000710c000000000000757c0000720c0000000000007f7c0000730c000000000000897c0000740c000000000000937c0000750c0000000000009d7c0000760c000000000000a77c0000770c000000000000b17c0000780c000000000000bb7c0000790c000000000000c57c00007a0c000000000000cf7c00007b0c000000000000d97c00007c0c000000000000e37c00007d0c000000000000ed7c00007e0c000000000000f77c00007f0c000000000000017d0000800c0000000000000b7d0000810c000000000000157d0000820c0000000000001f7d0000830c000000000000297d0000840c000000000000337d0000850c0000000000003d7d0000860c000000000000477d0000870c000000000000517d0000880c0000000000005b7d0000890c000000000000657d00008a0c0000000000006f7d00008b0c000000000000797d00008c0c000000000000837d00008d0c0000000000008d7d00008e0c000000000000977d00008f0c000000000000a17d0000900c000000000000ab7d0000910c000000000000b57d0000920c000000000000bf7d0000930c000000000000c97d0000940c000000000000d37d0000950c000000000000dd7d0000960c000000000000e77d0000970c000000000000f17d0000980c000000000000fb7d0000990c000000000000057e00009a0c0000000000000f7e00009b0c000000000000197e00009c0c000000000000237e00009d0c0000000000002d7e00009e0c000000000000377e00009f0c000000000000417e0000a00c0000000000004b7e0000a10c000000000000557e0000a20c0000000000005f7e0000a30c000000000000697e0000a40c000000000000737e0000a50c0000000000007d7e0000a60c000000000000877e0000a70c000000000000917e0000a80c0000000000009b7e0000a90c000000000000a57e0000aa0c000000000000af7e0000ab0c000000000000b97e0000ac0c000000000000c37e0000ad0c000000000000cd7e0000ae0c000000000000d77e0000af0c000000000000e17e0000b00c000000000000eb7e0000b10c000000000000f57e0000b20c000000000000ff7e0000b30c000000000000097f0000b40c000000000000137f0000b50c0000000000001d7f0000b60c000000000000277f0000b70c000000000000317f0000b80c0000000000003b7f0000b90c000000000000457f0000ba0c0000000000004f7f0000bb0c000000000000597f0000bc0c000000000000637f0000bd0c0000000000006d7f0000be0c000000000000777f0000bf0c000000000000817f0000c00c0000000000008b7f0000c10c000000000000957f0000c20c0000000000009f7f0000c30c000000000000a97f0000c40c000000000000b37f0000c50c000000000000bd7f0000c60c000000000000c77f0000c70c000000000000d17f0000c80c000000000000db7f0000c90c000000000000e57f0000ca0c000000000000ef7f0000cb0c000000000000f97f0000cc0c00000000000003800000cd0c0000000000000d800000ce0c00000000000017800000cf0c00000000000021800000d00c0000000000002b800000d10c00000000000035800000d20c0000000000003f800000d30c00000000000049800000d40c00000000000053800000d50c0000000000005d800000d60c00000000000067800000d70c00000000000071800000d80c0000000000007b800000d90c00000000000085800000da0c0000000000008f800000db0c00000000000099800000dc0c000000000000a3800000dd0c000000000000ad800000de0c000000000000b7800000df0c000000000000c1800000e00c000000000000cb800000e10c000000000000d5800000e20c000000000000df800000e30c000000000000e9800000e40c000000000000f3800000e50c000000000000fd800000e60c00000000000007810000e70c00000000000011810000e80c0000000000001b810000e90c00000000000025810000ea0c0000000000002f810000eb0c00000000000039810000ec0c00000000000043810000ed0c0000000000004d810000ee0c00000000000057810000ef0c00000000000061810000f00c0000000000006b810000f10c00000000000075810000f20c0000000000007f810000f30c00000000000089810000f40c00000000000093810000f50c0000000000009d810000f60c000000000000a7810000f70c000000000000b1810000f80c000000000000bb810000f90c000000000000c5810000fa0c000000000000cf810000fb0c000000000000d9810000fc0c000000000000e3810000fd0c000000000000ed810000fe0c000000000000f7810000ff0c00000000000001820000000d0000000000000b820000010d00000000000015820000020d0000000000001f820000030d00000000000029820000040d00000000000033820000050d0000000000003d820000060d00000000000047820000070d00000000000051820000080d0000000000005b820000090d000000000000658200000a0d0000000000006f8200000b0d000000000000798200000c0d000000000000838200000d0d0000000000008d8200000e0d000000000000978200000f0d000000000000a1820000100d000000000000ab820000110d000000000000b5820000120d000000000000bf820000130d000000000000c9820000140d000000000000d3820000150d000000000000dd820000160d000000000000e7820000170d000000000000f1820000180d000000000000fb820000190d000000000000058300001a0d0000000000000f8300001b0d000000000000198300001c0d000000000000238300001d0d0000000000002d8300001e0d000000000000378300001f0d00000000000041830000200d0000000000004b830000210d00000000000055830000220d0000000000005f830000230d00000000000069830000240d00000000000073830000250d0000000000007d830000260d00000000000087830000270d00000000000091830000280d0000000000009b830000290d000000000000a58300002a0d000000000000af8300002b0d000000000000b98300002c0d000000000000c38300002d0d000000000000cd8300002e0d000000000000d78300002f0d000000000000e1830000300d000000000000eb830000310d000000000000f5830000320d000000000000ff830000330d00000000000009840000340d00000000000013840000350d0000000000001d840000360d00000000000027840000370d00000000000031840000380d0000000000003b840000390d000000000000458400003a0d0000000000004f8400003b0d000000000000598400003c0d000000000000638400003d0d0000000000006d8400003e0d000000000000778400003f0d00000000000081840000400d0000000000008b840000410d00000000000095840000420d0000000000009f840000430d000000000000a9840000440d000000000000b3840000450d000000000000bd840000460d000000000000c7840000470d000000000000d1840000480d000000000000db840000490d000000000000e58400004a0d000000000000ef8400004b0d000000000000f98400004c0d000000000000038500004d0d0000000000000d8500004e0d000000000000178500004f0d00000000000021850000500d0000000000002b850000510d00000000000035850000520d0000000000003f850000530d00000000000049850000540d00000000000053850000550d0000000000005d850000560d00000000000067850000570d00000000000071850000580d0000000000007b850000590d000000000000858500005a0d0000000000008f8500005b0d000000000000998500005c0d000000000000a38500005d0d000000000000ad8500005e0d000000000000b78500005f0d000000000000c1850000600d000000000000cb850000610d000000000000d5850000620d000000000000df850000630d000000000000e9850000640d000000000000f3850000650d000000000000fd850000660d00000000000007860000670d00000000000011860000680d0000000000001b860000690d000000000000258600006a0d0000000000002f8600006b0d000000000000398600006c0d000000000000438600006d0d0000000000004d8600006e0d000000000000578600006f0d00000000000061860000700d0000000000006b860000710d00000000000075860000720d0000000000007f860000730d00000000000089860000740d00000000000093860000750d0000000000009d860000760d000000000000a7860000770d000000000000b1860000780d000000000000bb860000790d000000000000c58600007a0d000000000000cf8600007b0d000000000000d98600007c0d000000000000e38600007d0d000000000000ed8600007e0d000000000000f78600007f0d00000000000001870000800d0000000000000b870000810d00000000000015870000820d0000000000001f870000830d00000000000029870000840d00000000000033870000850d0000000000003d870000860d00000000000047870000870d00000000000051870000880d0000000000005b870000890d000000000000658700008a0d0000000000006f8700008b0d000000000000798700008c0d000000000000838700008d0d0000000000008d8700008e0d000000000000978700008f0d000000000000a1870000900d000000000000ab870000910d000000000000b5870000920d000000000000bf870000930d000000000000c9870000940d000000000000d3870000950d000000000000dd870000960d000000000000e7870000970d000000000000f1870000980d000000000000fb870000990d000000000000058800009a0d0000000000000f8800009b0d000000000000198800009c0d000000000000238800009d0d0000000000002d8800009e0d000000000000378800009f0d00000000000041880000a00d0000000000004b880000a10d00000000000055880000a20d0000000000005f880000a30d00000000000069880000a40d00000000000073880000a50d0000000000007d880000a60d00000000000087880000a70d00000000000091880000a80d0000000000009b880000a90d000000000000a5880000aa0d000000000000af880000ab0d000000000000b9880000ac0d000000000000c3880000ad0d000000000000cd880000ae0d000000000000d7880000af0d000000000000e1880000b00d000000000000eb880000b10d000000000000f5880000b20d000000000000ff880000b30d00000000000009890000b40d00000000000013890000b50d0000000000001d890000b60d00000000000027890000b70d00000000000031890000b80d0000000000003b890000b90d00000000000045890000ba0d0000000000004f890000bb0d00000000000059890000bc0d00000000000063890000bd0d0000000000006d890000be0d00000000000077890000bf0d00000000000081890000c00d0000000000008b890000c10d00000000000095890000c20d0000000000009f890000c30d000000000000a9890000c40d000000000000b3890000c50d000000000000bd890000c60d000000000000c7890000c70d000000000000d1890000c80d000000000000db890000c90d000000000000e5890000ca0d000000000000ef890000cb0d000000000000f9890000cc0d000000000000038a0000cd0d0000000000000d8a0000ce0d000000000000178a0000cf0d000000000000218a0000d00d0000000000002b8a0000d10d000000000000358a0000d20d0000000000003f8a0000d30d000000000000498a0000d40d000000000000538a0000d50d0000000000005d8a0000d60d000000000000678a0000d70d000000000000718a0000d80d0000000000007b8a0000d90d000000000000858a0000da0d0000000000008f8a0000db0d000000000000998a0000dc0d000000000000a38a0000dd0d000000000000ad8a0000de0d000000000000b78a0000df0d000000000000c18a0000e00d000000000000cb8a0000e10d000000000000d58a0000e20d000000000000df8a0000e30d000000000000e98a0000e40d000000000000f38a0000e50d000000000000fd8a0000e60d000000000000078b0000e70d000000000000118b0000e80d0000000000001b8b0000e90d000000000000258b0000ea0d0000000000002f8b0000eb0d000000000000398b0000ec0d000000000000438b0000ed0d0000000000004d8b0000ee0d000000000000578b0000ef0d000000000000618b0000f00d0000000000006b8b0000f10d000000000000758b0000f20d0000000000007f8b0000f30d000000000000898b0000f40d000000000000938b0000f50d0000000000009d8b0000f60d000000000000a78b0000f70d000000000000b18b0000f80d000000000000bb8b0000f90d000000000000c58b0000fa0d000000000000cf8b0000fb0d000000000000d98b0000fc0d000000000000e38b0000fd0d000000000000ed8b0000fe0d000000000000f78b0000ff0d000000000000018c0000000e0000000000000b8c0000010e000000000000158c0000020e0000000000001f8c0000030e000000000000298c0000040e000000000000338c0000050e0000000000003d8c0000060e000000000000478c0000070e000000000000518c0000080e0000000000005b8c0000090e000000000000658c00000a0e0000000000006f8c00000b0e000000000000798c00000c0e000000000000838c00000d0e0000000000008d8c00000e0e000000000000978c00000f0e000000000000a18c0000100e000000000000ab8c0000110e000000000000b58c0000120e000000000000bf8c0000130e000000000000c98c0000140e000000000000d38c0000150e000000000000dd8c0000160e000000000000e78c0000170e000000000000f18c0000180e000000000000fb8c0000190e000000000000058d00001a0e0000000000000f8d00001b0e000000000000198d00001c0e000000000000238d00001d0e0000000000002d8d00001e0e000000000000378d00001f0e000000000000418d0000200e0000000000004b8d0000210e000000000000558d0000220e0000000000005f8d0000230e000000000000698d0000240e000000000000738d0000250e0000000000007d8d0000260e000000000000878d0000270e000000000000918d0000280e0000000000009b8d0000290e000000000000a58d00002a0e000000000000af8d00002b0e000000000000b98d00002c0e000000000000c38d00002d0e000000000000cd8d00002e0e000000000000d78d00002f0e000000000000e18d0000300e000000000000eb8d0000310e000000000000f58d0000320e000000000000ff8d0000330e000000000000098e0000340e000000000000138e0000350e0000000000001d8e0000360e000000000000278e0000370e000000000000318e0000380e0000000000003b8e0000390e000000000000458e00003a0e0000000000004f8e00003b0e000000000000598e00003c0e000000000000638e00003d0e0000000000006d8e00003e0e000000000000778e00003f0e000000000000818e0000400e0000000000008b8e0000410e000000000000958e0000420e0000000000009f8e0000430e000000000000a98e0000440e000000000000b38e0000450e000000000000bd8e0000460e000000000000c78e0000470e000000000000d18e0000480e000000000000db8e0000490e000000000000e58e00004a0e000000000000ef8e00004b0e000000000000f98e00004c0e000000000000038f00004d0e0000000000000d8f00004e0e000000000000178f00004f0e000000000000218f0000500e0000000000002b8f0000510e000000000000358f0000520e0000000000003f8f0000530e000000000000498f0000540e000000000000538f0000550e0000000000005d8f0000560e000000000000678f0000570e000000000000718f0000580e0000000000007b8f0000590e000000000000858f00005a0e0000000000008f8f00005b0e000000000000998f00005c0e000000000000a38f00005d0e000000000000ad8f00005e0e000000000000b78f00005f0e000000000000c18f0000600e000000000000cb8f0000610e000000000000d58f0000620e000000000000df8f0000630e000000000000e98f0000640e000000000000f38f0000650e000000000000fd8f0000660e00000000000007900000670e00000000000011900000680e0000000000001b900000690e000000000000259000006a0e0000000000002f9000006b0e000000000000399000006c0e000000000000439000006d0e0000000000004d9000006e0e000000000000579000006f0e00000000000061900000700e0000000000006b900000710e00000000000075900000720e0000000000007f900000730e00000000000089900000740e00000000000093900000750e0000000000009d900000760e000000000000a7900000770e000000000000b1900000780e000000000000bb900000790e000000000000c59000007a0e000000000000cf9000007b0e000000000000d99000007c0e000000000000e39000007d0e000000000000ed9000007e0e000000000000f79000007f0e00000000000001910000800e0000000000000b910000810e00000000000015910000820e0000000000001f910000830e00000000000029910000840e00000000000033910000850e0000000000003d910000860e00000000000047910000870e00000000000051910000880e0000000000005b910000890e000000000000659100008a0e0000000000006f9100008b0e000000000000799100008c0e000000000000839100008d0e0000000000008d9100008e0e000000000000979100008f0e000000000000a1910000900e000000000000ab910000910e000000000000b5910000920e000000000000bf910000930e000000000000c9910000940e000000000000d3910000950e000000000000dd910000960e000000000000e7910000970e000000000000f1910000980e000000000000fb910000990e000000000000059200009a0e0000000000000f9200009b0e000000000000199200009c0e000000000000239200009d0e0000000000002d9200009e0e000000000000379200009f0e00000000000041920000a00e0000000000004b920000a10e00000000000055920000a20e0000000000005f920000a30e00000000000069920000a40e00000000000073920000a50e0000000000007d920000a60e00000000000087920000a70e00000000000091920000a80e0000000000009b920000a90e000000000000a5920000aa0e000000000000af920000ab0e000000000000b9920000ac0e000000000000c3920000ad0e000000000000cd920000ae0e000000000000d7920000af0e000000000000e1920000b00e000000000000eb920000b10e000000000000f5920000b20e000000000000ff920000b30e00000000000009930000b40e00000000000013930000b50e0000000000001d930000b60e00000000000027930000b70e00000000000031930000b80e0000000000003b930000b90e00000000000045930000ba0e0000000000004f930000bb0e00000000000059930000bc0e00000000000063930000bd0e0000000000006d930000be0e00000000000077930000bf0e00000000000081930000c00e0000000000008b930000c10e00000000000095930000c20e0000000000009f930000c30e000000000000a9930000c40e000000000000b3930000c50e000000000000bd930000c60e000000000000c7930000c70e000000000000d1930000c80e000000000000db930000c90e000000000000e5930000ca0e000000000000ef930000cb0e000000000000f9930000cc0e00000000000003940000cd0e0000000000000d940000ce0e00000000000017940000cf0e00000000000021940000d00e0000000000002b940000d10e00000000000035940000d20e0000000000003f940000d30e00000000000049940000d40e00000000000053940000d50e0000000000005d940000d60e00000000000067940000d70e00000000000071940000d80e0000000000007b940000d90e00000000000085940000da0e0000000000008f940000db0e00000000000099940000dc0e000000000000a3940000dd0e000000000000ad940000de0e000000000000b7940000df0e000000000000c1940000e00e000000000000cb940000e10e000000000000d5940000e20e000000000000df940000e30e000000000000e9940000e40e000000000000f3940000e50e000000000000fd940000e60e00000000000007950000e70e00000000000011950000e80e0000000000001b950000e90e00000000000025950000ea0e0000000000002f950000eb0e00000000000039950000ec0e00000000000043950000ed0e0000000000004d950000ee0e00000000000057950000ef0e00000000000061950000f00e0000000000006b950000f10e00000000000075950000f20e0000000000007f950000f30e00000000000089950000f40e00000000000093950000f50e0000000000009d950000f60e000000000000a7950000f70e000000000000b1950000f80e000000000000bb950000f90e000000000000c5950000fa0e000000000000cf950000fb0e000000000000d9950000fc0e000000000000e3950000fd0e000000000000ed950000fe0e000000000000f7950000ff0e00000000000001960000000f0000000000000b960000010f00000000000015960000020f0000000000001f960000030f00000000000029960000040f00000000000033960000050f0000000000003d960000060f00000000000047960000070f00000000000051960000080f0000000000005b960000090f000000000000659600000a0f0000000000006f9600000b0f000000000000799600000c0f000000000000839600000d0f0000000000008d9600000e0f000000000000979600000f0f000000000000a1960000100f000000000000ab960000110f000000000000b5960000120f000000000000bf960000130f000000000000c9960000140f000000000000d3960000150f000000000000dd960000160f000000000000e7960000170f000000000000f1960000180f000000000000fb960000190f000000000000059700001a0f0000000000000f9700001b0f000000000000199700001c0f000000000000239700001d0f0000000000002d9700001e0f000000000000379700001f0f00000000000041970000200f0000000000004b970000210f00000000000055970000220f0000000000005f970000230f00000000000069970000240f00000000000073970000250f0000000000007d970000260f00000000000087970000270f00000000000091970000280f0000000000009b970000290f000000000000a59700002a0f000000000000af9700002b0f000000000000b99700002c0f000000000000c39700002d0f000000000000cd9700002e0f000000000000d79700002f0f000000000000e1970000300f000000000000eb970000310f000000000000f5970000320f000000000000ff970000330f00000000000009980000340f00000000000013980000350f0000000000001d980000360f00000000000027980000370f00000000000031980000380f0000000000003b980000390f000000000000459800003a0f0000000000004f9800003b0f000000000000599800003c0f000000000000639800003d0f0000000000006d9800003e0f000000000000779800003f0f00000000000081980000400f0000000000008b980000410f00000000000095980000420f0000000000009f980000430f000000000000a9980000440f000000000000b3980000450f000000000000bd980000460f000000000000c7980000470f000000000000d1980000480f000000000000db980000490f000000000000e59800004a0f000000000000ef9800004b0f000000000000f99800004c0f000000000000039900004d0f0000000000000d9900004e0f000000000000179900004f0f00000000000021990000500f0000000000002b990000510f00000000000035990000520f0000000000003f990000530f00000000000049990000540f00000000000053990000550f0000000000005d990000560f00000000000067990000570f00000000000071990000580f0000000000007b990000590f000000000000859900005a0f0000000000008f9900005b0f000000000000999900005c0f000000000000a39900005d0f000000000000ad9900005e0f000000000000b79900005f0f000000000000c1990000600f000000000000cb990000610f000000000000d5990000620f000000000000df990000630f000000000000e9990000640f000000000000f3990000650f000000000000fd990000660f000000000000079a0000670f000000000000119a0000680f0000000000001b9a0000690f000000000000259a00006a0f0000000000002f9a00006b0f000000000000399a00006c0f000000000000439a00006d0f0000000000004d9a00006e0f000000000000579a00006f0f000000000000619a0000700f0000000000006b9a0000710f000000000000759a0000720f0000000000007f9a0000730f000000000000899a0000740f000000000000939a0000750f0000000000009d9a0000760f000000000000a79a0000770f000000000000b19a0000780f000000000000bb9a0000790f000000000000c59a00007a0f000000000000cf9a00007b0f000000000000d99a00007c0f000000000000e39a00007d0f000000000000ed9a00007e0f000000000000f79a00007f0f000000000000019b0000800f0000000000000b9b0000810f000000000000159b0000820f0000000000001f9b0000830f000000000000299b0000840f000000000000339b0000850f0000000000003d9b0000860f000000000000479b0000870f000000000000519b0000880f0000000000005b9b0000890f000000000000659b00008a0f0000000000006f9b00008b0f000000000000799b00008c0f000000000000839b00008d0f0000000000008d9b00008e0f000000000000979b00008f0f000000000000a19b0000900f000000000000ab9b0000910f000000000000b59b0000920f000000000000bf9b0000930f000000000000c99b0000940f000000000000d39b0000950f000000000000dd9b0000960f000000000000e79b0000970f000000000000f19b0000980f000000000000fb9b0000990f000000000000059c00009a0f0000000000000f9c00009b0f000000000000199c00009c0f000000000000239c00009d0f0000000000002d9c00009e0f000000000000379c00009f0f000000000000419c0000a00f0000000000004b9c0000a10f000000000000559c0000a20f0000000000005f9c0000a30f000000000000699c0000a40f000000000000739c0000a50f0000000000007d9c0000a60f000000000000879c0000a70f000000000000919c0000a80f0000000000009b9c0000a90f000000000000a59c0000aa0f000000000000af9c0000ab0f000000000000b99c0000ac0f000000000000c39c0000ad0f000000000000cd9c0000ae0f000000000000d79c0000af0f000000000000e19c0000b00f000000000000eb9c0000b10f000000000000f59c0000b20f000000000000ff9c0000b30f000000000000099d0000b40f000000000000139d0000b50f0000000000001d9d0000b60f000000000000279d0000b70f000000000000319d0000b80f0000000000003b9d0000b90f000000000000459d0000ba0f0000000000004f9d0000bb0f000000000000599d0000bc0f000000000000639d0000bd0f0000000000006d9d0000be0f000000000000779d0000bf0f000000000000819d0000c00f0000000000008b9d0000c10f000000000000959d0000c20f0000000000009f9d0000c30f000000000000a99d0000c40f000000000000b39d0000c50f000000000000bd9d0000c60f000000000000c79d0000c70f000000000000d19d0000c80f000000000000db9d0000c90f000000000000e59d0000ca0f000000000000ef9d0000cb0f000000000000f99d0000cc0f000000000000039e0000cd0f0000000000000d9e0000ce0f000000000000179e0000cf0f000000000000219e0000d00f0000000000002b9e0000d10f000000000000359e0000d20f0000000000003f9e0000d30f000000000000499e0000d40f000000000000539e0000d50f0000000000005d9e0000d60f000000000000679e0000d70f000000000000719e0000d80f0000000000007b9e0000d90f000000000000859e0000da0f0000000000008f9e0000db0f000000000000999e0000dc0f000000000000a39e0000dd0f000000000000ad9e0000de0f000000000000b79e0000df0f000000000000c19e0000e00f000000000000cb9e0000e10f000000000000d59e0000e20f000000000000df9e0000e30f000000000000e99e0000e40f000000000000f39e0000e50f000000000000fd9e0000e60f000000000000079f0000e70f000000000000119f0000e80f0000000000001b9f0000e90f000000000000259f0000ea0f0000000000002f9f0000eb0f000000000000399f0000ec0f000000000000439f0000ed0f0000000000004d9f0000ee0f000000000000579f0000ef0f000000000000619f0000f00f0000000000006b9f0000f10f000000000000759f0000f20f0000000000007f9f0000f30f000000000000899f0000f40f000000000000939f0000f50f0000000000009d9f0000f60f000000000000a79f0000f70f000000000000b19f0000f80f000000000000bb9f0000f90f000000000000c59f0000fa0f000000000000cf9f0000fb0f000000000000d99f0000fc0f000000000000e39f0000fd0f000000000000ed9f0000fe0f000000000000f79f0000ff0f00000000000001a0000000100000000000000ba00000011000000000000015a0000002100000000000001fa00000031000000000000029a00000041000000000000033a0000005100000000000003da00000061000000000000047a00000071000000000000051a0000008100000000000005ba00000091000000000000065a000000a100000000000006fa000000b1000000000000079a000000c1000000000000083a000000d100000000000008da000000e1000000000000097a000000f10000000000000a1a000001010000000000000aba000001110000000000000b5a000001210000000000000bfa000001310000000000000c9a000001410000000000000d3a000001510000000000000dda000001610000000000000e7a000001710000000000000f1a000001810000000000000fba00000191000000000000005a100001a100000000000000fa100001b1000000000000019a100001c1000000000000023a100001d100000000000002da100001e1000000000000037a100001f1000000000000041a1000020100000000000004ba10000211000000000000055a1000022100000000000005fa10000231000000000000069a10000241000000000000073a1000025100000000000007da10000261000000000000087a10000271000000000000091a1000028100000000000009ba100002910000000000000a5a100002a10000000000000afa100002b10000000000000b9a100002c10000000000000c3a100002d10000000000000cda100002e10000000000000d7a100002f10000000000000e1a100003010000000000000eba100003110000000000000f5a100003210000000000000ffa10000331000000000000009a20000341000000000000013a2000035100000000000001da20000361000000000000027a20000371000000000000031a2000038100000000000003ba20000391000000000000045a200003a100000000000004fa200003b1000000000000059a200003c1000000000000063a200003d100000000000006da200003e1000000000000077a200003f1000000000000081a2000040100000000000008ba20000411000000000000095a2000042100000000000009fa200004310000000000000a9a200004410000000000000b3a200004510000000000000bda200004610000000000000c7a200004710000000000000d1a200004810000000000000dba200004910000000000000e5a200004a10000000000000efa200004b10000000000000f9a200004c1000000000000003a300004d100000000000000da300004e1000000000000017a300004f1000000000000021a3000050100000000000002ba30000511000000000000035a3000052100000000000003fa30000531000000000000049a30000541000000000000053a3000055100000000000005da30000561000000000000067a30000571000000000000071a3000058100000000000007ba30000591000000000000085a300005a100000000000008fa300005b1000000000000099a300005c10000000000000a3a300005d10000000000000ada300005e10000000000000b7a300005f10000000000000c1a300006010000000000000cba300006110000000000000d5a300006210000000000000dfa300006310000000000000e9a300006410000000000000f3a300006510000000000000fda30000661000000000000007a40000671000000000000011a4000068100000000000001ba40000691000000000000025a400006a100000000000002fa400006b1000000000000039a400006c1000000000000043a400006d100000000000004da400006e1000000000000057a400006f1000000000000061a4000070100000000000006ba40000711000000000000075a4000072100000000000007fa40000731000000000000089a40000741000000000000093a4000075100000000000009da400007610000000000000a7a400007710000000000000b1a400007810000000000000bba400007910000000000000c5a400007a10000000000000cfa400007b10000000000000d9a400007c10000000000000e3a400007d10000000000000eda400007e10000000000000f7a400007f1000000000000001a5000080100000000000000ba50000811000000000000015a5000082100000000000001fa50000831000000000000029a50000841000000000000033a5000085100000000000003da50000861000000000000047a50000871000000000000051a5000088100000000000005ba50000891000000000000065a500008a100000000000006fa500008b1000000000000079a500008c1000000000000083a500008d100000000000008da500008e1000000000000097a500008f10000000000000a1a500009010000000000000aba500009110000000000000b5a500009210000000000000bfa500009310000000000000c9a500009410000000000000d3a500009510000000000000dda500009610000000000000e7a500009710000000000000f1a500009810000000000000fba50000991000000000000005a600009a100000000000000fa600009b1000000000000019a600009c1000000000000023a600009d100000000000002da600009e1000000000000037a600009f1000000000000041a60000a0100000000000004ba60000a11000000000000055a60000a2100000000000005fa60000a31000000000000069a60000a41000000000000073a60000a5100000000000007da60000a61000000000000087a60000a71000000000000091a60000a8100000000000009ba60000a910000000000000a5a60000aa10000000000000afa60000ab10000000000000b9a60000ac10000000000000c3a60000ad10000000000000cda60000ae10000000000000d7a60000af10000000000000e1a60000b010000000000000eba60000b110000000000000f5a60000b210000000000000ffa60000b31000000000000009a70000b41000000000000013a70000b5100000000000001da70000b61000000000000027a70000b71000000000000031a70000b8100000000000003ba70000b91000000000000045a70000ba100000000000004fa70000bb1000000000000059a70000bc1000000000000063a70000bd100000000000006da70000be1000000000000077a70000bf1000000000000081a70000c0100000000000008ba70000c11000000000000095a70000c2100000000000009fa70000c310000000000000a9a70000c410000000000000b3a70000c510000000000000bda70000c610000000000000c7a70000c710000000000000d1a70000c810000000000000dba70000c910000000000000e5a70000ca10000000000000efa70000cb10000000000000f9a70000cc1000000000000003a80000cd100000000000000da80000ce1000000000000017a80000cf1000000000000021a80000d0100000000000002ba80000d11000000000000035a80000d2100000000000003fa80000d31000000000000049a80000d41000000000000053a80000d5100000000000005da80000d61000000000000067a80000d71000000000000071a80000d8100000000000007ba80000d91000000000000085a80000da100000000000008fa80000db1000000000000099a80000dc10000000000000a3a80000dd10000000000000ada80000de10000000000000b7a80000df10000000000000c1a80000e010000000000000cba80000e110000000000000d5a80000e210000000000000dfa80000e310000000000000e9a80000e410000000000000f3a80000e510000000000000fda80000e61000000000000007a90000e71000000000000011a90000e8100000000000001ba90000e91000000000000025a90000ea100000000000002fa90000eb1000000000000039a90000ec1000000000000043a90000ed100000000000004da90000ee1000000000000057a90000ef1000000000000061a90000f0100000000000006ba90000f11000000000000075a90000f2100000000000007fa90000f31000000000000089a90000f41000000000000093a90000f5100000000000009da90000f610000000000000a7a90000f710000000000000b1a90000f810000000000000bba90000f910000000000000c5a90000fa10000000000000cfa90000fb10000000000000d9a90000fc10000000000000e3a90000fd10000000000000eda90000fe10000000000000f7a90000ff1000000000000001aa000000110000000000000baa0000011100000000000015aa000002110000000000001faa0000031100000000000029aa0000041100000000000033aa000005110000000000003daa0000061100000000000047aa0000071100000000000051aa000008110000000000005baa0000091100000000000065aa00000a110000000000006faa00000b1100000000000079aa00000c1100000000000083aa00000d110000000000008daa00000e1100000000000097aa00000f11000000000000a1aa00001011000000000000abaa00001111000000000000b5aa00001211000000000000bfaa00001311000000000000c9aa00001411000000000000d3aa00001511000000000000ddaa00001611000000000000e7aa00001711000000000000f1aa00001811000000000000fbaa0000191100000000000005ab00001a110000000000000fab00001b1100000000000019ab00001c1100000000000023ab00001d110000000000002dab00001e1100000000000037ab00001f1100000000000041ab000020110000000000004bab0000211100000000000055ab000022110000000000005fab0000231100000000000069ab0000241100000000000073ab000025110000000000007dab0000261100000000000087ab0000271100000000000091ab000028110000000000009bab00002911000000000000a5ab00002a11000000000000afab00002b11000000000000b9ab00002c11000000000000c3ab00002d11000000000000cdab00002e11000000000000d7ab00002f11000000000000e1ab00003011000000000000ebab00003111000000000000f5ab00003211000000000000ffab0000331100000000000009ac0000341100000000000013ac000035110000000000001dac0000361100000000000027ac0000371100000000000031ac000038110000000000003bac0000391100000000000045ac00003a110000000000004fac00003b1100000000000059ac00003c1100000000000063ac00003d110000000000006dac00003e1100000000000077ac00003f1100000000000081ac000040110000000000008bac0000411100000000000095ac000042110000000000009fac00004311000000000000a9ac00004411000000000000b3ac00004511000000000000bdac00004611000000000000c7ac00004711000000000000d1ac00004811000000000000dbac00004911000000000000e5ac00004a11000000000000efac00004b11000000000000f9ac00004c1100000000000003ad00004d110000000000000dad00004e1100000000000017ad00004f1100000000000021ad000050110000000000002bad0000511100000000000035ad000052110000000000003fad0000531100000000000049ad0000541100000000000053ad000055110000000000005dad0000561100000000000067ad0000571100000000000071ad000058110000000000007bad0000591100000000000085ad00005a110000000000008fad00005b1100000000000099ad00005c11000000000000a3ad00005d11000000000000adad00005e11000000000000b7ad00005f11000000000000c1ad00006011000000000000cbad00006111000000000000d5ad00006211000000000000dfad00006311000000000000e9ad00006411000000000000f3ad00006511000000000000fdad0000661100000000000007ae0000671100000000000011ae000068110000000000001bae0000691100000000000025ae00006a110000000000002fae00006b1100000000000039ae00006c1100000000000043ae00006d110000000000004dae00006e1100000000000057ae00006f1100000000000061ae000070110000000000006bae0000711100000000000075ae000072110000000000007fae0000731100000000000089ae0000741100000000000093ae000075110000000000009dae00007611000000000000a7ae00007711000000000000b1ae00007811000000000000bbae00007911000000000000c5ae00007a11000000000000cfae00007b11000000000000d9ae00007c11000000000000e3ae00007d11000000000000edae00007e11000000000000f7ae00007f1100000000000001af000080110000000000000baf0000811100000000000015af000082110000000000001faf0000831100000000000029af0000841100000000000033af000085110000000000003daf0000861100000000000047af0000871100000000000051af000088110000000000005baf0000891100000000000065af00008a110000000000006faf00008b1100000000000079af00008c1100000000000083af00008d110000000000008daf00008e1100000000000097af00008f11000000000000a1af00009011000000000000abaf00009111000000000000b5af00009211000000000000bfaf00009311000000000000c9af00009411000000000000d3af00009511000000000000ddaf00009611000000000000e7af00009711000000000000f1af00009811000000000000fbaf0000991100000000000005b000009a110000000000000fb000009b1100000000000019b000009c1100000000000023b000009d110000000000002db000009e1100000000000037b000009f1100000000000041b00000a0110000000000004bb00000a11100000000000055b00000a2110000000000005fb00000a31100000000000069b00000a41100000000000073b00000a5110000000000007db00000a61100000000000087b00000a71100000000000091b00000a8110000000000009bb00000a911000000000000a5b00000aa11000000000000afb00000ab11000000000000b9b00000ac11000000000000c3b00000ad11000000000000cdb00000ae11000000000000d7b00000af11000000000000e1b00000b011000000000000ebb00000b111000000000000f5b00000b211000000000000ffb00000b31100000000000009b10000b41100000000000013b10000b5110000000000001db10000b61100000000000027b10000b71100000000000031b10000b8110000000000003bb10000b91100000000000045b10000ba110000000000004fb10000bb1100000000000059b10000bc1100000000000063b10000bd110000000000006db10000be1100000000000077b10000bf1100000000000081b10000c0110000000000008bb10000c11100000000000095b10000c2110000000000009fb10000c311000000000000a9b10000c411000000000000b3b10000c511000000000000bdb10000c611000000000000c7b10000c711000000000000d1b10000c811000000000000dbb10000c911000000000000e5b10000ca11000000000000efb10000cb11000000000000f9b10000cc1100000000000003b20000cd110000000000000db20000ce1100000000000017b20000cf1100000000000021b20000d0110000000000002bb20000d11100000000000035b20000d2110000000000003fb20000d31100000000000049b20000d41100000000000053b20000d5110000000000005db20000d61100000000000067b20000d71100000000000071b20000d8110000000000007bb20000d91100000000000085b20000da110000000000008fb20000db1100000000000099b20000dc11000000000000a3b20000dd11000000000000adb20000de11000000000000b7b20000df11000000000000c1b20000e011000000000000cbb20000e111000000000000d5b20000e211000000000000dfb20000e311000000000000e9b20000e411000000000000f3b20000e511000000000000fdb20000e61100000000000007b30000e71100000000000011b30000e8110000000000001bb30000e91100000000000025b30000ea110000000000002fb30000eb1100000000000039b30000ec1100000000000043b30000ed110000000000004db30000ee1100000000000057b30000ef1100000000000061b30000f0110000000000006bb30000f11100000000000075b30000f2110000000000007fb30000f31100000000000089b30000f41100000000000093b30000f5110000000000009db30000f611000000000000a7b30000f711000000000000b1b30000f811000000000000bbb30000f911000000000000c5b30000fa11000000000000cfb30000fb11000000000000d9b30000fc11000000000000e3b30000fd11000000000000edb30000fe11000000000000f7b30000ff1100000000000001b4000000120000000000000bb40000011200000000000015b4000002120000000000001fb40000031200000000000029b40000041200000000000033b4000005120000000000003db40000061200000000000047b40000071200000000000051b4000008120000000000005bb40000091200000000000065b400000a120000000000006fb400000b1200000000000079b400000c1200000000000083b400000d120000000000008db400000e1200000000000097b400000f12000000000000a1b400001012000000000000abb400001112000000000000b5b400001212000000000000bfb400001312000000000000c9b400001412000000000000d3b400001512000000000000ddb400001612000000000000e7b400001712000000000000f1b400001812000000000000fbb40000191200000000000005b500001a120000000000000fb500001b1200000000000019b500001c1200000000000023b500001d120000000000002db500001e1200000000000037b500001f1200000000000041b5000020120000000000004bb50000211200000000000055b5000022120000000000005fb50000231200000000000069b50000241200000000000073b5000025120000000000007db50000261200000000000087b50000271200000000000091b5000028120000000000009bb500002912000000000000a5b500002a12000000000000afb500002b12000000000000b9b500002c12000000000000c3b500002d12000000000000cdb500002e12000000000000d7b500002f12000000000000e1b500003012000000000000ebb500003112000000000000f5b500003212000000000000ffb50000331200000000000009b60000341200000000000013b6000035120000000000001db60000361200000000000027b60000371200000000000031b6000038120000000000003bb60000391200000000000045b600003a120000000000004fb600003b1200000000000059b600003c1200000000000063b600003d120000000000006db600003e1200000000000077b600003f1200000000000081b6000040120000000000008bb60000411200000000000095b6000042120000000000009fb600004312000000000000a9b600004412000000000000b3b600004512000000000000bdb600004612000000000000c7b600004712000000000000d1b600004812000000000000dbb600004912000000000000e5b600004a12000000000000efb600004b12000000000000f9b600004c1200000000000003b700004d120000000000000db700004e1200000000000017b700004f1200000000000021b7000050120000000000002bb70000511200000000000035b7000052120000000000003fb70000531200000000000049b70000541200000000000053b7000055120000000000005db70000561200000000000067b70000571200000000000071b7000058120000000000007bb70000591200000000000085b700005a120000000000008fb700005b1200000000000099b700005c12000000000000a3b700005d12000000000000adb700005e12000000000000b7b700005f12000000000000c1b700006012000000000000cbb700006112000000000000d5b700006212000000000000dfb700006312000000000000e9b700006412000000000000f3b700006512000000000000fdb70000661200000000000007b80000671200000000000011b8000068120000000000001bb80000691200000000000025b800006a120000000000002fb800006b1200000000000039b800006c1200000000000043b800006d120000000000004db800006e1200000000000057b800006f1200000000000061b8000070120000000000006bb80000711200000000000075b8000072120000000000007fb80000731200000000000089b80000741200000000000093b8000075120000000000009db800007612000000000000a7b800007712000000000000b1b800007812000000000000bbb800007912000000000000c5b800007a12000000000000cfb800007b12000000000000d9b800007c12000000000000e3b800007d12000000000000edb800007e12000000000000f7b800007f1200000000000001b9000080120000000000000bb90000811200000000000015b9000082120000000000001fb90000831200000000000029b90000841200000000000033b9000085120000000000003db90000861200000000000047b90000871200000000000051b9000088120000000000005bb90000891200000000000065b900008a120000000000006fb900008b1200000000000079b900008c1200000000000083b900008d120000000000008db900008e1200000000000097b900008f12000000000000a1b900009012000000000000abb900009112000000000000b5b900009212000000000000bfb900009312000000000000c9b900009412000000000000d3b900009512000000000000ddb900009612000000000000e7b900009712000000000000f1b900009812000000000000fbb90000991200000000000005ba00009a120000000000000fba00009b1200000000000019ba00009c1200000000000023ba00009d120000000000002dba00009e1200000000000037ba00009f1200000000000041ba0000a0120000000000004bba0000a11200000000000055ba0000a2120000000000005fba0000a31200000000000069ba0000a41200000000000073ba0000a5120000000000007dba0000a61200000000000087ba0000a71200000000000091ba0000a8120000000000009bba0000a912000000000000a5ba0000aa12000000000000afba0000ab12000000000000b9ba0000ac12000000000000c3ba0000ad12000000000000cdba0000ae12000000000000d7ba0000af12000000000000e1ba0000b012000000000000ebba0000b112000000000000f5ba0000b212000000000000ffba0000b31200000000000009bb0000b41200000000000013bb0000b5120000000000001dbb0000b61200000000000027bb0000b71200000000000031bb0000b8120000000000003bbb0000b91200000000000045bb0000ba120000000000004fbb0000bb1200000000000059bb0000bc1200000000000063bb0000bd120000000000006dbb0000be1200000000000077bb0000bf1200000000000081bb0000c0120000000000008bbb0000c11200000000000095bb0000c2120000000000009fbb0000c312000000000000a9bb0000c412000000000000b3bb0000c512000000000000bdbb0000c612000000000000c7bb0000c712000000000000d1bb0000c812000000000000dbbb0000c912000000000000e5bb0000ca12000000000000efbb0000cb12000000000000f9bb0000cc1200000000000003bc0000cd120000000000000dbc0000ce1200000000000017bc0000cf1200000000000021bc0000d0120000000000002bbc0000d11200000000000035bc0000d2120000000000003fbc0000d31200000000000049bc0000d41200000000000053bc0000d5120000000000005dbc0000d61200000000000067bc0000d71200000000000071bc0000d8120000000000007bbc0000d91200000000000085bc0000da120000000000008fbc0000db1200000000000099bc0000dc12000000000000a3bc0000dd12000000000000adbc0000de12000000000000b7bc0000df12000000000000c1bc0000e012000000000000cbbc0000e112000000000000d5bc0000e212000000000000dfbc0000e312000000000000e9bc0000e412000000000000f3bc0000e512000000000000fdbc0000e61200000000000007bd0000e71200000000000011bd0000e8120000000000001bbd0000e91200000000000025bd0000ea120000000000002fbd0000eb1200000000000039bd0000ec1200000000000043bd0000ed120000000000004dbd0000ee1200000000000057bd0000ef1200000000000061bd0000f0120000000000006bbd0000f11200000000000075bd0000f2120000000000007fbd0000f31200000000000089bd0000f41200000000000093bd0000f5120000000000009dbd0000f612000000000000a7bd0000f712000000000000b1bd0000f812000000000000bbbd0000f912000000000000c5bd0000fa12000000000000cfbd0000fb12000000000000d9bd0000fc12000000000000e3bd0000fd12000000000000edbd0000fe12000000000000f7bd0000ff1200000000000001be000000130000000000000bbe0000011300000000000015be000002130000000000001fbe0000031300000000000029be0000041300000000000033be000005130000000000003dbe0000061300000000000047be0000071300000000000051be000008130000000000005bbe0000091300000000000065be00000a130000000000006fbe00000b1300000000000079be00000c1300000000000083be00000d130000000000008dbe00000e1300000000000097be00000f13000000000000a1be00001013000000000000abbe00001113000000000000b5be00001213000000000000bfbe00001313000000000000c9be00001413000000000000d3be00001513000000000000ddbe00001613000000000000e7be00001713000000000000f1be00001813000000000000fbbe0000191300000000000005bf00001a130000000000000fbf00001b1300000000000019bf00001c1300000000000023bf00001d130000000000002dbf00001e1300000000000037bf00001f1300000000000041bf000020130000000000004bbf0000211300000000000055bf000022130000000000005fbf0000231300000000000069bf0000241300000000000073bf000025130000000000007dbf0000261300000000000087bf0000271300000000000091bf000028130000000000009bbf00002913000000000000a5bf00002a13000000000000afbf00002b13000000000000b9bf00002c13000000000000c3bf00002d13000000000000cdbf00002e13000000000000d7bf00002f13000000000000e1bf00003013000000000000ebbf00003113000000000000f5bf00003213000000000000ffbf0000331300000000000009c00000341300000000000013c0000035130000000000001dc00000361300000000000027c00000371300000000000031c0000038130000000000003bc00000391300000000000045c000003a130000000000004fc000003b1300000000000059c000003c1300000000000063c000003d130000000000006dc000003e1300000000000077c000003f1300000000000081c0000040130000000000008bc00000411300000000000095c0000042130000000000009fc000004313000000000000a9c000004413000000000000b3c000004513000000000000bdc000004613000000000000c7c000004713000000000000d1c000004813000000000000dbc000004913000000000000e5c000004a13000000000000efc000004b13000000000000f9c000004c1300000000000003c100004d130000000000000dc100004e1300000000000017c100004f1300000000000021c1000050130000000000002bc10000511300000000000035c1000052130000000000003fc10000531300000000000049c10000541300000000000053c1000055130000000000005dc10000561300000000000067c10000571300000000000071c1000058130000000000007bc10000591300000000000085c100005a130000000000008fc100005b1300000000000099c100005c13000000000000a3c100005d13000000000000adc100005e13000000000000b7c100005f13000000000000c1c100006013000000000000cbc100006113000000000000d5c100006213000000000000dfc100006313000000000000e9c100006413000000000000f3c100006513000000000000fdc10000661300000000000007c20000671300000000000011c2000068130000000000001bc20000691300000000000025c200006a130000000000002fc200006b1300000000000039c200006c1300000000000043c200006d130000000000004dc200006e1300000000000057c200006f1300000000000061c2000070130000000000006bc20000711300000000000075c2000072130000000000007fc20000731300000000000089c20000741300000000000093c2000075130000000000009dc200007613000000000000a7c200007713000000000000b1c200007813000000000000bbc200007913000000000000c5c200007a13000000000000cfc200007b13000000000000d9c200007c13000000000000e3c200007d13000000000000edc200007e13000000000000f7c200007f1300000000000001c3000080130000000000000bc30000811300000000000015c3000082130000000000001fc30000831300000000000029c30000841300000000000033c3000085130000000000003dc30000861300000000000047c30000871300000000000051c3000088130000000000005bc30000891300000000000065c300008a130000000000006fc300008b1300000000000079c300008c1300000000000083c300008d130000000000008dc300008e1300000000000097c300008f13000000000000a1c300009013000000000000abc300009113000000000000b5c300009213000000000000bfc300009313000000000000c9c300009413000000000000d3c300009513000000000000ddc300009613000000000000e7c300009713000000000000f1c300009813000000000000fbc30000991300000000000005c400009a130000000000000fc400009b1300000000000019c400009c1300000000000023c400009d130000000000002dc400009e1300000000000037c400009f1300000000000041c40000a0130000000000004bc40000a11300000000000055c40000a2130000000000005fc40000a31300000000000069c40000a41300000000000073c40000a5130000000000007dc40000a61300000000000087c40000a71300000000000091c40000a8130000000000009bc40000a913000000000000a5c40000aa13000000000000afc40000ab13000000000000b9c40000ac13000000000000c3c40000ad13000000000000cdc40000ae13000000000000d7c40000af13000000000000e1c40000b013000000000000ebc40000b113000000000000f5c40000b213000000000000ffc40000b31300000000000009c50000b41300000000000013c50000b5130000000000001dc50000b61300000000000027c50000b71300000000000031c50000b8130000000000003bc50000b91300000000000045c50000ba130000000000004fc50000bb1300000000000059c50000bc1300000000000063c50000bd130000000000006dc50000be1300000000000077c50000bf1300000000000081c50000c0130000000000008bc50000c11300000000000095c50000c2130000000000009fc50000c313000000000000a9c50000c413000000000000b3c50000c513000000000000bdc50000c613000000000000c7c50000c713000000000000d1c50000c813000000000000dbc50000c913000000000000e5c50000ca13000000000000efc50000cb13000000000000f9c50000cc1300000000000003c60000cd130000000000000dc60000ce1300000000000017c60000cf1300000000000021c60000d0130000000000002bc60000d11300000000000035c60000d2130000000000003fc60000d31300000000000049c60000d41300000000000053c60000d5130000000000005dc60000d61300000000000067c60000d71300000000000071c60000d8130000000000007bc60000d91300000000000085c60000da130000000000008fc60000db1300000000000099c60000dc13000000000000a3c60000dd13000000000000adc60000de13000000000000b7c60000df13000000000000c1c60000e013000000000000cbc60000e113000000000000d5c60000e213000000000000dfc60000e313000000000000e9c60000e413000000000000f3c60000e513000000000000fdc60000e61300000000000007c70000e71300000000000011c70000e8130000000000001bc70000e91300000000000025c70000ea130000000000002fc70000eb1300000000000039c70000ec1300000000000043c70000ed130000000000004dc70000ee1300000000000057c70000ef1300000000000061c70000f0130000000000006bc70000f11300000000000075c70000f2130000000000007fc70000f31300000000000089c70000f41300000000000093c70000f5130000000000009dc70000f613000000000000a7c70000f713000000000000b1c70000f813000000000000bbc70000f913000000000000c5c70000fa13000000000000cfc70000fb13000000000000d9c70000fc13000000000000e3c70000fd13000000000000edc70000fe13000000000000f7c70000ff1300000000000001c8000000140000000000000bc80000011400000000000015c8000002140000000000001fc80000031400000000000029c80000041400000000000033c8000005140000000000003dc80000061400000000000047c80000071400000000000051c8000008140000000000005bc80000091400000000000065c800000a140000000000006fc800000b1400000000000079c800000c1400000000000083c800000d140000000000008dc800000e1400000000000097c800000f14000000000000a1c800001014000000000000abc800001114000000000000b5c800001214000000000000bfc800001314000000000000c9c800001414000000000000d3c800001514000000000000ddc800001614000000000000e7c800001714000000000000f1c800001814000000000000fbc80000191400000000000005c900001a140000000000000fc900001b1400000000000019c900001c1400000000000023c900001d140000000000002dc900001e1400000000000037c900001f1400000000000041c9000020140000000000004bc90000211400000000000055c9000022140000000000005fc90000231400000000000069c90000241400000000000073c9000025140000000000007dc90000261400000000000087c90000271400000000000091c9000028140000000000009bc900002914000000000000a5c900002a14000000000000afc900002b14000000000000b9c900002c14000000000000c3c900002d14000000000000cdc900002e14000000000000d7c900002f14000000000000e1c900003014000000000000ebc900003114000000000000f5c900003214000000000000ffc90000331400000000000009ca0000341400000000000013ca000035140000000000001dca0000361400000000000027ca0000371400000000000031ca000038140000000000003bca0000391400000000000045ca00003a140000000000004fca00003b1400000000000059ca00003c1400000000000063ca00003d140000000000006dca00003e1400000000000077ca00003f1400000000000081ca000040140000000000008bca0000411400000000000095ca000042140000000000009fca00004314000000000000a9ca00004414000000000000b3ca00004514000000000000bdca00004614000000000000c7ca00004714000000000000d1ca00004814000000000000dbca00004914000000000000e5ca00004a14000000000000efca00004b14000000000000f9ca00004c1400000000000003cb00004d140000000000000dcb00004e1400000000000017cb00004f1400000000000021cb000050140000000000002bcb0000511400000000000035cb000052140000000000003fcb0000531400000000000049cb0000541400000000000053cb000055140000000000005dcb0000561400000000000067cb0000571400000000000071cb000058140000000000007bcb0000591400000000000085cb00005a140000000000008fcb00005b1400000000000099cb00005c14000000000000a3cb00005d14000000000000adcb00005e14000000000000b7cb00005f14000000000000c1cb00006014000000000000cbcb00006114000000000000d5cb00006214000000000000dfcb00006314000000000000e9cb00006414000000000000f3cb00006514000000000000fdcb0000661400000000000007cc0000671400000000000011cc000068140000000000001bcc0000691400000000000025cc00006a140000000000002fcc00006b1400000000000039cc00006c1400000000000043cc00006d140000000000004dcc00006e1400000000000057cc00006f1400000000000061cc000070140000000000006bcc0000711400000000000075cc000072140000000000007fcc0000731400000000000089cc0000741400000000000093cc000075140000000000009dcc00007614000000000000a7cc00007714000000000000b1cc00007814000000000000bbcc00007914000000000000c5cc00007a14000000000000cfcc00007b14000000000000d9cc00007c14000000000000e3cc00007d14000000000000edcc00007e14000000000000f7cc00007f1400000000000001cd000080140000000000000bcd0000811400000000000015cd000082140000000000001fcd0000831400000000000029cd0000841400000000000033cd000085140000000000003dcd0000861400000000000047cd0000871400000000000051cd000088140000000000005bcd0000891400000000000065cd00008a140000000000006fcd00008b1400000000000079cd00008c1400000000000083cd00008d140000000000008dcd00008e1400000000000097cd00008f14000000000000a1cd00009014000000000000abcd00009114000000000000b5cd00009214000000000000bfcd00009314000000000000c9cd00009414000000000000d3cd00009514000000000000ddcd00009614000000000000e7cd00009714000000000000f1cd00009814000000000000fbcd0000991400000000000005ce00009a140000000000000fce00009b1400000000000019ce00009c1400000000000023ce00009d140000000000002dce00009e1400000000000037ce00009f1400000000000041ce0000a0140000000000004bce0000a11400000000000055ce0000a2140000000000005fce0000a31400000000000069ce0000a41400000000000073ce0000a5140000000000007dce0000a61400000000000087ce0000a71400000000000091ce0000a8140000000000009bce0000a914000000000000a5ce0000aa14000000000000afce0000ab14000000000000b9ce0000ac14000000000000c3ce0000ad14000000000000cdce0000ae14000000000000d7ce0000af14000000000000e1ce0000b014000000000000ebce0000b114000000000000f5ce0000b214000000000000ffce0000b31400000000000009cf0000b41400000000000013cf0000b5140000000000001dcf0000b61400000000000027cf0000b71400000000000031cf0000b8140000000000003bcf0000b91400000000000045cf0000ba140000000000004fcf0000bb1400000000000059cf0000bc1400000000000063cf0000bd140000000000006dcf0000be1400000000000077cf0000bf1400000000000081cf0000c0140000000000008bcf0000c11400000000000095cf0000c2140000000000009fcf0000c314000000000000a9cf0000c414000000000000b3cf0000c514000000000000bdcf0000c614000000000000c7cf0000c714000000000000d1cf0000c814000000000000dbcf0000c914000000000000e5cf0000ca14000000000000efcf0000cb14000000000000f9cf0000cc1400000000000003d00000cd140000000000000dd00000ce1400000000000017d00000cf1400000000000021d00000d0140000000000002bd00000d11400000000000035d00000d2140000000000003fd00000d31400000000000049d00000d41400000000000053d00000d5140000000000005dd00000d61400000000000067d00000d71400000000000071d00000d8140000000000007bd00000d91400000000000085d00000da140000000000008fd00000db1400000000000099d00000dc14000000000000a3d00000dd14000000000000add00000de14000000000000b7d00000df14000000000000c1d00000e014000000000000cbd00000e114000000000000d5d00000e214000000000000dfd00000e314000000000000e9d00000e414000000000000f3d00000e514000000000000fdd00000e61400000000000007d10000e71400000000000011d10000e8140000000000001bd10000e91400000000000025d10000ea140000000000002fd10000eb1400000000000039d10000ec1400000000000043d10000ed140000000000004dd10000ee1400000000000057d10000ef1400000000000061d10000f0140000000000006bd10000f11400000000000075d10000f2140000000000007fd10000f31400000000000089d10000f41400000000000093d10000f5140000000000009dd10000f614000000000000a7d10000f714000000000000b1d10000f814000000000000bbd10000f914000000000000c5d10000fa14000000000000cfd10000fb14000000000000d9d10000fc14000000000000e3d10000fd14000000000000edd10000fe14000000000000f7d10000ff1400000000000001d2000000150000000000000bd20000011500000000000015d2000002150000000000001fd20000031500000000000029d20000041500000000000033d2000005150000000000003dd20000061500000000000047d20000071500000000000051d2000008150000000000005bd20000091500000000000065d200000a150000000000006fd200000b1500000000000079d200000c1500000000000083d200000d150000000000008dd200000e1500000000000097d200000f15000000000000a1d200001015000000000000abd200001115000000000000b5d200001215000000000000bfd200001315000000000000c9d200001415000000000000d3d200001515000000000000ddd200001615000000000000e7d200001715000000000000f1d200001815000000000000fbd20000191500000000000005d300001a150000000000000fd300001b1500000000000019d300001c1500000000000023d300001d150000000000002dd300001e1500000000000037d300001f1500000000000041d3000020150000000000004bd30000211500000000000055d3000022150000000000005fd30000231500000000000069d30000241500000000000073d3000025150000000000007dd30000261500000000000087d30000271500000000000091d3000028150000000000009bd300002915000000000000a5d300002a15000000000000afd300002b15000000000000b9d300002c15000000000000c3d300002d15000000000000cdd300002e15000000000000d7d300002f15000000000000e1d300003015000000000000ebd300003115000000000000f5d300003215000000000000ffd30000331500000000000009d40000341500000000000013d4000035150000000000001dd40000361500000000000027d40000371500000000000031d4000038150000000000003bd40000391500000000000045d400003a150000000000004fd400003b1500000000000059d400003c1500000000000063d400003d150000000000006dd400003e1500000000000077d400003f1500000000000081d4000040150000000000008bd40000411500000000000095d4000042150000000000009fd400004315000000000000a9d400004415000000000000b3d400004515000000000000bdd400004615000000000000c7d400004715000000000000d1d400004815000000000000dbd400004915000000000000e5d400004a15000000000000efd400004b15000000000000f9d400004c1500000000000003d500004d150000000000000dd500004e1500000000000017d500004f1500000000000021d5000050150000000000002bd50000511500000000000035d5000052150000000000003fd50000531500000000000049d50000541500000000000053d5000055150000000000005dd50000561500000000000067d50000571500000000000071d5000058150000000000007bd50000591500000000000085d500005a150000000000008fd500005b1500000000000099d500005c15000000000000a3d500005d15000000000000add500005e15000000000000b7d500005f15000000000000c1d500006015000000000000cbd500006115000000000000d5d500006215000000000000dfd500006315000000000000e9d500006415000000000000f3d500006515000000000000fdd50000661500000000000007d60000671500000000000011d6000068150000000000001bd60000691500000000000025d600006a150000000000002fd600006b1500000000000039d600006c1500000000000043d600006d150000000000004dd600006e1500000000000057d600006f1500000000000061d6000070150000000000006bd60000711500000000000075d6000072150000000000007fd60000731500000000000089d60000741500000000000093d6000075150000000000009dd600007615000000000000a7d600007715000000000000b1d600007815000000000000bbd600007915000000000000c5d600007a15000000000000cfd600007b15000000000000d9d600007c15000000000000e3d600007d15000000000000edd600007e15000000000000f7d600007f1500000000000001d7000080150000000000000bd70000811500000000000015d7000082150000000000001fd70000831500000000000029d70000841500000000000033d7000085150000000000003dd70000861500000000000047d70000871500000000000051d7000088150000000000005bd70000891500000000000065d700008a150000000000006fd700008b1500000000000079d700008c1500000000000083d700008d150000000000008dd700008e1500000000000097d700008f15000000000000a1d700009015000000000000abd700009115000000000000b5d700009215000000000000bfd700009315000000000000c9d700009415000000000000d3d700009515000000000000ddd700009615000000000000e7d700009715000000000000f1d700009815000000000000fbd70000991500000000000005d800009a150000000000000fd800009b1500000000000019d800009c1500000000000023d800009d150000000000002dd800009e1500000000000037d800009f1500000000000041d80000a0150000000000004bd80000a11500000000000055d80000a2150000000000005fd80000a31500000000000069d80000a41500000000000073d80000a5150000000000007dd80000a61500000000000087d80000a71500000000000091d80000a8150000000000009bd80000a915000000000000a5d80000aa15000000000000afd80000ab15000000000000b9d80000ac15000000000000c3d80000ad15000000000000cdd80000ae15000000000000d7d80000af15000000000000e1d80000b015000000000000ebd80000b115000000000000f5d80000b215000000000000ffd80000b31500000000000009d90000b41500000000000013d90000b5150000000000001dd90000b61500000000000027d90000b71500000000000031d90000b8150000000000003bd90000b91500000000000045d90000ba150000000000004fd90000bb1500000000000059d90000bc1500000000000063d90000bd150000000000006dd90000be1500000000000077d90000bf1500000000000081d90000c0150000000000008bd90000c11500000000000095d90000c2150000000000009fd90000c315000000000000a9d90000c415000000000000b3d90000c515000000000000bdd90000c615000000000000c7d90000c715000000000000d1d90000c815000000000000dbd90000c915000000000000e5d90000ca15000000000000efd90000cb15000000000000f9d90000cc1500000000000003da0000cd150000000000000dda0000ce1500000000000017da0000cf1500000000000021da0000d0150000000000002bda0000d11500000000000035da0000d2150000000000003fda0000d31500000000000049da0000d41500000000000053da0000d5150000000000005dda0000d61500000000000067da0000d71500000000000071da0000d8150000000000007bda0000d91500000000000085da0000da150000000000008fda0000db1500000000000099da0000dc15000000000000a3da0000dd15000000000000adda0000de15000000000000b7da0000df15000000000000c1da0000e015000000000000cbda0000e115000000000000d5da0000e215000000000000dfda0000e315000000000000e9da0000e415000000000000f3da0000e515000000000000fdda0000e61500000000000007db0000e71500000000000011db0000e8150000000000001bdb0000e91500000000000025db0000ea150000000000002fdb0000eb1500000000000039db0000ec1500000000000043db0000ed150000000000004ddb0000ee1500000000000057db0000ef1500000000000061db0000f0150000000000006bdb0000f11500000000000075db0000f2150000000000007fdb0000f31500000000000089db0000f41500000000000093db0000f5150000000000009ddb0000f615000000000000a7db0000f715000000000000b1db0000f815000000000000bbdb0000f915000000000000c5db0000fa15000000000000cfdb0000fb15000000000000d9db0000fc15000000000000e3db0000fd15000000000000eddb0000fe15000000000000f7db0000ff1500000000000001dc000000160000000000000bdc0000011600000000000015dc000002160000000000001fdc0000031600000000000029dc0000041600000000000033dc000005160000000000003ddc0000061600000000000047dc0000071600000000000051dc000008160000000000005bdc0000091600000000000065dc00000a160000000000006fdc00000b1600000000000079dc00000c1600000000000083dc00000d160000000000008ddc00000e1600000000000097dc00000f16000000000000a1dc00001016000000000000abdc00001116000000000000b5dc00001216000000000000bfdc00001316000000000000c9dc00001416000000000000d3dc00001516000000000000dddc00001616000000000000e7dc00001716000000000000f1dc00001816000000000000fbdc0000191600000000000005dd00001a160000000000000fdd00001b1600000000000019dd00001c1600000000000023dd00001d160000000000002ddd00001e1600000000000037dd00001f1600000000000041dd000020160000000000004bdd0000211600000000000055dd000022160000000000005fdd0000231600000000000069dd0000241600000000000073dd000025160000000000007ddd0000261600000000000087dd0000271600000000000091dd000028160000000000009bdd00002916000000000000a5dd00002a16000000000000afdd00002b16000000000000b9dd00002c16000000000000c3dd00002d16000000000000cddd00002e16000000000000d7dd00002f16000000000000e1dd00003016000000000000ebdd00003116000000000000f5dd00003216000000000000ffdd0000331600000000000009de0000341600000000000013de000035160000000000001dde0000361600000000000027de0000371600000000000031de000038160000000000003bde0000391600000000000045de00003a160000000000004fde00003b1600000000000059de00003c1600000000000063de00003d160000000000006dde00003e1600000000000077de00003f1600000000000081de000040160000000000008bde0000411600000000000095de000042160000000000009fde00004316000000000000a9de00004416000000000000b3de00004516000000000000bdde00004616000000000000c7de00004716000000000000d1de00004816000000000000dbde00004916000000000000e5de00004a16000000000000efde00004b16000000000000f9de00004c1600000000000003df00004d160000000000000ddf00004e1600000000000017df00004f1600000000000021df000050160000000000002bdf0000511600000000000035df000052160000000000003fdf0000531600000000000049df0000541600000000000053df000055160000000000005ddf0000561600000000000067df0000571600000000000071df000058160000000000007bdf0000591600000000000085df00005a160000000000008fdf00005b1600000000000099df00005c16000000000000a3df00005d16000000000000addf00005e16000000000000b7df00005f16000000000000c1df00006016000000000000cbdf00006116000000000000d5df00006216000000000000dfdf00006316000000000000e9df00006416000000000000f3df00006516000000000000fddf0000661600000000000007e00000671600000000000011e0000068160000000000001be00000691600000000000025e000006a160000000000002fe000006b1600000000000039e000006c1600000000000043e000006d160000000000004de000006e1600000000000057e000006f1600000000000061e0000070160000000000006be00000711600000000000075e0000072160000000000007fe00000731600000000000089e00000741600000000000093e0000075160000000000009de000007616000000000000a7e000007716000000000000b1e000007816000000000000bbe000007916000000000000c5e000007a16000000000000cfe000007b16000000000000d9e000007c16000000000000e3e000007d16000000000000ede000007e16000000000000f7e000007f1600000000000001e1000080160000000000000be10000811600000000000015e1000082160000000000001fe10000831600000000000029e10000841600000000000033e1000085160000000000003de10000861600000000000047e10000871600000000000051e1000088160000000000005be10000891600000000000065e100008a160000000000006fe100008b1600000000000079e100008c1600000000000083e100008d160000000000008de100008e1600000000000097e100008f16000000000000a1e100009016000000000000abe100009116000000000000b5e100009216000000000000bfe100009316000000000000c9e100009416000000000000d3e100009516000000000000dde100009616000000000000e7e100009716000000000000f1e100009816000000000000fbe10000991600000000000005e200009a160000000000000fe200009b1600000000000019e200009c1600000000000023e200009d160000000000002de200009e1600000000000037e200009f1600000000000041e20000a0160000000000004be20000a11600000000000055e20000a2160000000000005fe20000a31600000000000069e20000a41600000000000073e20000a5160000000000007de20000a61600000000000087e20000a71600000000000091e20000a8160000000000009be20000a916000000000000a5e20000aa16000000000000afe20000ab16000000000000b9e20000ac16000000000000c3e20000ad16000000000000cde20000ae16000000000000d7e20000af16000000000000e1e20000b016000000000000ebe20000b116000000000000f5e20000b216000000000000ffe20000b31600000000000009e30000b41600000000000013e30000b5160000000000001de30000b61600000000000027e30000b71600000000000031e30000b8160000000000003be30000b91600000000000045e30000ba160000000000004fe30000bb1600000000000059e30000bc1600000000000063e30000bd160000000000006de30000be1600000000000077e30000bf1600000000000081e30000c0160000000000008be30000c11600000000000095e30000c2160000000000009fe30000c316000000000000a9e30000c416000000000000b3e30000c516000000000000bde30000c616000000000000c7e30000c716000000000000d1e30000c816000000000000dbe30000c916000000000000e5e30000ca16000000000000efe30000cb16000000000000f9e30000cc1600000000000003e40000cd160000000000000de40000ce1600000000000017e40000cf1600000000000021e40000d0160000000000002be40000d11600000000000035e40000d2160000000000003fe40000d31600000000000049e40000d41600000000000053e40000d5160000000000005de40000d61600000000000067e40000d71600000000000071e40000d8160000000000007be40000d91600000000000085e40000da160000000000008fe40000db1600000000000099e40000dc16000000000000a3e40000dd16000000000000ade40000de16000000000000b7e40000df16000000000000c1e40000e016000000000000cbe40000e116000000000000d5e40000e216000000000000dfe40000e316000000000000e9e40000e416000000000000f3e40000e516000000000000fde40000e61600000000000007e50000e71600000000000011e50000e8160000000000001be50000e91600000000000025e50000ea160000000000002fe50000eb1600000000000039e50000ec1600000000000043e50000ed160000000000004de50000ee1600000000000057e50000ef1600000000000061e50000f0160000000000006be50000f11600000000000075e50000f2160000000000007fe50000f31600000000000089e50000f41600000000000093e50000f5160000000000009de50000f616000000000000a7e50000f716000000000000b1e50000f816000000000000bbe50000f916000000000000c5e50000fa16000000000000cfe50000fb16000000000000d9e50000fc16000000000000e3e50000fd16000000000000ede50000fe16000000000000f7e50000ff1600000000000001e6000000170000000000000be60000011700000000000015e6000002170000000000001fe60000031700000000000029e60000041700000000000033e6000005170000000000003de60000061700000000000047e60000071700000000000051e6000008170000000000005be60000091700000000000065e600000a170000000000006fe600000b1700000000000079e600000c1700000000000083e600000d170000000000008de600000e1700000000000097e600000f17000000000000a1e600001017000000000000abe600001117000000000000b5e600001217000000000000bfe600001317000000000000c9e600001417000000000000d3e600001517000000000000dde600001617000000000000e7e600001717000000000000f1e600001817000000000000fbe60000191700000000000005e700001a170000000000000fe700001b1700000000000019e700001c1700000000000023e700001d170000000000002de700001e1700000000000037e700001f1700000000000041e7000020170000000000004be70000211700000000000055e7000022170000000000005fe70000231700000000000069e70000241700000000000073e7000025170000000000007de70000261700000000000087e70000271700000000000091e7000028170000000000009be700002917000000000000a5e700002a17000000000000afe700002b17000000000000b9e700002c17000000000000c3e700002d17000000000000cde700002e17000000000000d7e700002f17000000000000e1e700003017000000000000ebe700003117000000000000f5e700003217000000000000ffe70000331700000000000009e80000341700000000000013e8000035170000000000001de80000361700000000000027e80000371700000000000031e8000038170000000000003be80000391700000000000045e800003a170000000000004fe800003b1700000000000059e800003c1700000000000063e800003d170000000000006de800003e1700000000000077e800003f1700000000000081e8000040170000000000008be80000411700000000000095e8000042170000000000009fe800004317000000000000a9e800004417000000000000b3e800004517000000000000bde800004617000000000000c7e800004717000000000000d1e800004817000000000000dbe800004917000000000000e5e800004a17000000000000efe800004b17000000000000f9e800004c1700000000000003e900004d170000000000000de900004e1700000000000017e900004f1700000000000021e9000050170000000000002be90000511700000000000035e9000052170000000000003fe90000531700000000000049e90000541700000000000053e9000055170000000000005de90000561700000000000067e90000571700000000000071e9000058170000000000007be90000591700000000000085e900005a170000000000008fe900005b1700000000000099e900005c17000000000000a3e900005d17000000000000ade900005e17000000000000b7e900005f17000000000000c1e900006017000000000000cbe900006117000000000000d5e900006217000000000000dfe900006317000000000000e9e900006417000000000000f3e900006517000000000000fde90000661700000000000007ea0000671700000000000011ea000068170000000000001bea0000691700000000000025ea00006a170000000000002fea00006b1700000000000039ea00006c1700000000000043ea00006d170000000000004dea00006e1700000000000057ea00006f1700000000000061ea000070170000000000006bea0000711700000000000075ea000072170000000000007fea0000731700000000000089ea0000741700000000000093ea000075170000000000009dea00007617000000000000a7ea00007717000000000000b1ea00007817000000000000bbea00007917000000000000c5ea00007a17000000000000cfea00007b17000000000000d9ea00007c17000000000000e3ea00007d17000000000000edea00007e17000000000000f7ea00007f1700000000000001eb000080170000000000000beb0000811700000000000015eb000082170000000000001feb0000831700000000000029eb0000841700000000000033eb000085170000000000003deb0000861700000000000047eb0000871700000000000051eb000088170000000000005beb0000891700000000000065eb00008a170000000000006feb00008b1700000000000079eb00008c1700000000000083eb00008d170000000000008deb00008e1700000000000097eb00008f17000000000000a1eb00009017000000000000abeb00009117000000000000b5eb00009217000000000000bfeb00009317000000000000c9eb00009417000000000000d3eb00009517000000000000ddeb00009617000000000000e7eb00009717000000000000f1eb00009817000000000000fbeb0000991700000000000005ec00009a170000000000000fec00009b1700000000000019ec00009c1700000000000023ec00009d170000000000002dec00009e1700000000000037ec00009f1700000000000041ec0000a0170000000000004bec0000a11700000000000055ec0000a2170000000000005fec0000a31700000000000069ec0000a41700000000000073ec0000a5170000000000007dec0000a61700000000000087ec0000a71700000000000091ec0000a8170000000000009bec0000a917000000000000a5ec0000aa17000000000000afec0000ab17000000000000b9ec0000ac17000000000000c3ec0000ad17000000000000cdec0000ae17000000000000d7ec0000af17000000000000e1ec0000b017000000000000ebec0000b117000000000000f5ec0000b217000000000000ffec0000b31700000000000009ed0000b41700000000000013ed0000b5170000000000001ded0000b61700000000000027ed0000b71700000000000031ed0000b8170000000000003bed0000b91700000000000045ed0000ba170000000000004fed0000bb1700000000000059ed0000bc1700000000000063ed0000bd170000000000006ded0000be1700000000000077ed0000bf1700000000000081ed0000c0170000000000008bed0000c11700000000000095ed0000c2170000000000009fed0000c317000000000000a9ed0000c417000000000000b3ed0000c517000000000000bded0000c617000000000000c7ed0000c717000000000000d1ed0000c817000000000000dbed0000c917000000000000e5ed0000ca17000000000000efed0000cb17000000000000f9ed0000cc1700000000000003ee0000cd170000000000000dee0000ce1700000000000017ee0000cf1700000000000021ee0000d0170000000000002bee0000d11700000000000035ee0000d2170000000000003fee0000d31700000000000049ee0000d41700000000000053ee0000d5170000000000005dee0000d61700000000000067ee0000d71700000000000071ee0000d8170000000000007bee0000d91700000000000085ee0000da170000000000008fee0000db1700000000000099ee0000dc17000000000000a3ee0000dd17000000000000adee0000de17000000000000b7ee0000df17000000000000c1ee0000e017000000000000cbee0000e117000000000000d5ee0000e217000000000000dfee0000e317000000000000e9ee0000e417000000000000f3ee0000e517000000000000fdee0000e61700000000000007ef0000e71700000000000011ef0000e8170000000000001bef0000e91700000000000025ef0000ea170000000000002fef0000eb1700000000000039ef0000ec1700000000000043ef0000ed170000000000004def0000ee1700000000000057ef0000ef1700000000000061ef0000f0170000000000006bef0000f11700000000000075ef0000f2170000000000007fef0000f31700000000000089ef0000f41700000000000093ef0000f5170000000000009def0000f617000000000000a7ef0000f717000000000000b1ef0000f817000000000000bbef0000f917000000000000c5ef0000fa17000000000000cfef0000fb17000000000000d9ef0000fc17000000000000e3ef0000fd17000000000000edef0000fe17000000000000f7ef0000ff1700000000000001f0000000180000000000000bf00000011800000000000015f0000002180000000000001ff00000031800000000000029f00000041800000000000033f0000005180000000000003df00000061800000000000047f00000071800000000000051f0000008180000000000005bf00000091800000000000065f000000a180000000000006ff000000b1800000000000079f000000c1800000000000083f000000d180000000000008df000000e1800000000000097f000000f18000000000000a1f000001018000000000000abf000001118000000000000b5f000001218000000000000bff000001318000000000000c9f000001418000000000000d3f000001518000000000000ddf000001618000000000000e7f000001718000000000000f1f000001818000000000000fbf00000191800000000000005f100001a180000000000000ff100001b1800000000000019f100001c1800000000000023f100001d180000000000002df100001e1800000000000037f100001f1800000000000041f1000020180000000000004bf10000211800000000000055f1000022180000000000005ff10000231800000000000069f10000241800000000000073f1000025180000000000007df10000261800000000000087f10000271800000000000091f1000028180000000000009bf100002918000000000000a5f100002a18000000000000aff100002b18000000000000b9f100002c18000000000000c3f100002d18000000000000cdf100002e18000000000000d7f100002f18000000000000e1f100003018000000000000ebf100003118000000000000f5f100003218000000000000fff10000331800000000000009f20000341800000000000013f2000035180000000000001df20000361800000000000027f20000371800000000000031f2000038180000000000003bf20000391800000000000045f200003a180000000000004ff200003b1800000000000059f200003c1800000000000063f200003d180000000000006df200003e1800000000000077f200003f1800000000000081f2000040180000000000008bf20000411800000000000095f2000042180000000000009ff200004318000000000000a9f200004418000000000000b3f200004518000000000000bdf200004618000000000000c7f200004718000000000000d1f200004818000000000000dbf200004918000000000000e5f200004a18000000000000eff200004b18000000000000f9f200004c1800000000000003f300004d180000000000000df300004e1800000000000017f300004f1800000000000021f3000050180000000000002bf30000511800000000000035f3000052180000000000003ff30000531800000000000049f30000541800000000000053f3000055180000000000005df30000561800000000000067f30000571800000000000071f3000058180000000000007bf30000591800000000000085f300005a180000000000008ff300005b1800000000000099f300005c18000000000000a3f300005d18000000000000adf300005e18000000000000b7f300005f18000000000000c1f300006018000000000000cbf300006118000000000000d5f300006218000000000000dff300006318000000000000e9f300006418000000000000f3f300006518000000000000fdf30000661800000000000007f40000671800000000000011f4000068180000000000001bf40000691800000000000025f400006a180000000000002ff400006b1800000000000039f400006c1800000000000043f400006d180000000000004df400006e1800000000000057f400006f1800000000000061f4000070180000000000006bf40000711800000000000075f4000072180000000000007ff40000731800000000000089f40000741800000000000093f4000075180000000000009df400007618000000000000a7f400007718000000000000b1f400007818000000000000bbf400007918000000000000c5f400007a18000000000000cff400007b18000000000000d9f400007c18000000000000e3f400007d18000000000000edf400007e18000000000000f7f400007f1800000000000001f5000080180000000000000bf50000811800000000000015f5000082180000000000001ff50000831800000000000029f50000841800000000000033f5000085180000000000003df50000861800000000000047f50000871800000000000051f5000088180000000000005bf50000891800000000000065f500008a180000000000006ff500008b1800000000000079f500008c1800000000000083f500008d180000000000008df500008e1800000000000097f500008f18000000000000a1f500009018000000000000abf500009118000000000000b5f500009218000000000000bff500009318000000000000c9f500009418000000000000d3f500009518000000000000ddf500009618000000000000e7f500009718000000000000f1f500009818000000000000fbf50000991800000000000005f600009a180000000000000ff600009b1800000000000019f600009c1800000000000023f600009d180000000000002df600009e1800000000000037f600009f1800000000000041f60000a0180000000000004bf60000a11800000000000055f60000a2180000000000005ff60000a31800000000000069f60000a41800000000000073f60000a5180000000000007df60000a61800000000000087f60000a71800000000000091f60000a8180000000000009bf60000a918000000000000a5f60000aa18000000000000aff60000ab18000000000000b9f60000ac18000000000000c3f60000ad18000000000000cdf60000ae18000000000000d7f60000af18000000000000e1f60000b018000000000000ebf60000b118000000000000f5f60000b218000000000000fff60000b31800000000000009f70000b41800000000000013f70000b5180000000000001df70000b61800000000000027f70000b71800000000000031f70000b8180000000000003bf70000b91800000000000045f70000ba180000000000004ff70000bb1800000000000059f70000bc1800000000000063f70000bd180000000000006df70000be1800000000000077f70000bf1800000000000081f70000c0180000000000008bf70000c11800000000000095f70000c2180000000000009ff70000c318000000000000a9f70000c418000000000000b3f70000c518000000000000bdf70000c618000000000000c7f70000c718000000000000d1f70000c818000000000000dbf70000c918000000000000e5f70000ca18000000000000eff70000cb18000000000000f9f70000cc1800000000000003f80000cd180000000000000df80000ce1800000000000017f80000cf1800000000000021f80000d0180000000000002bf80000d11800000000000035f80000d2180000000000003ff80000d31800000000000049f80000d41800000000000053f80000d5180000000000005df80000d61800000000000067f80000d71800000000000071f80000d8180000000000007bf80000d91800000000000085f80000da180000000000008ff80000db1800000000000099f80000dc18000000000000a3f80000dd18000000000000adf80000de18000000000000b7f80000df18000000000000c1f80000e018000000000000cbf80000e118000000000000d5f80000e218000000000000dff80000e318000000000000e9f80000e418000000000000f3f80000e518000000000000fdf80000e61800000000000007f90000e71800000000000011f90000e8180000000000001bf90000e91800000000000025f90000ea180000000000002ff90000eb1800000000000039f90000ec1800000000000043f90000ed180000000000004df90000ee1800000000000057f90000ef1800000000000061f90000f0180000000000006bf90000f11800000000000075f90000f2180000000000007ff90000f31800000000000089f90000f41800000000000093f90000f5180000000000009df90000f618000000000000a7f90000f718000000000000b1f90000f818000000000000bbf90000f918000000000000c5f90000fa18000000000000cff90000fb18000000000000d9f90000fc18000000000000e3f90000fd18000000000000edf90000fe18000000000000f7f90000ff1800000000000001fa000000190000000000000bfa0000011900000000000015fa000002190000000000001ffa0000031900000000000029fa0000041900000000000033fa000005190000000000003dfa0000061900000000000047fa0000071900000000000051fa000008190000000000005bfa0000091900000000000065fa00000a190000000000006ffa00000b1900000000000079fa00000c1900000000000083fa00000d190000000000008dfa00000e1900000000000097fa00000f19000000000000a1fa00001019000000000000abfa00001119000000000000b5fa00001219000000000000bffa00001319000000000000c9fa00001419000000000000d3fa00001519000000000000ddfa00001619000000000000e7fa00001719000000000000f1fa00001819000000000000fbfa0000191900000000000005fb00001a190000000000000ffb00001b1900000000000019fb00001c1900000000000023fb00001d190000000000002dfb00001e1900000000000037fb00001f1900000000000041fb000020190000000000004bfb0000211900000000000055fb000022190000000000005ffb0000231900000000000069fb0000241900000000000073fb000025190000000000007dfb0000261900000000000087fb0000271900000000000091fb000028190000000000009bfb00002919000000000000a5fb00002a19000000000000affb00002b19000000000000b9fb00002c19000000000000c3fb00002d19000000000000cdfb00002e19000000000000d7fb00002f19000000000000e1fb00003019000000000000ebfb00003119000000000000f5fb00003219000000000000fffb0000331900000000000009fc0000341900000000000013fc000035190000000000001dfc0000361900000000000027fc0000371900000000000031fc000038190000000000003bfc0000391900000000000045fc00003a190000000000004ffc00003b1900000000000059fc00003c1900000000000063fc00003d190000000000006dfc00003e1900000000000077fc00003f1900000000000081fc000040190000000000008bfc0000411900000000000095fc000042190000000000009ffc00004319000000000000a9fc00004419000000000000b3fc00004519000000000000bdfc00004619000000000000c7fc00004719000000000000d1fc00004819000000000000dbfc00004919000000000000e5fc00004a19000000000000effc00004b19000000000000f9fc00004c1900000000000003fd00004d190000000000000dfd00004e1900000000000017fd00004f1900000000000021fd000050190000000000002bfd0000511900000000000035fd000052190000000000003ffd0000531900000000000049fd0000541900000000000053fd000055190000000000005dfd0000561900000000000067fd0000571900000000000071fd000058190000000000007bfd0000591900000000000085fd00005a190000000000008ffd00005b1900000000000099fd00005c19000000000000a3fd00005d19000000000000adfd00005e19000000000000b7fd00005f19000000000000c1fd00006019000000000000cbfd00006119000000000000d5fd00006219000000000000dffd00006319000000000000e9fd00006419000000000000f3fd00006519000000000000fdfd0000661900000000000007fe0000671900000000000011fe000068190000000000001bfe0000691900000000000025fe00006a190000000000002ffe00006b1900000000000039fe00006c1900000000000043fe00006d190000000000004dfe00006e1900000000000057fe00006f1900000000000061fe000070190000000000006bfe0000711900000000000075fe000072190000000000007ffe0000731900000000000089fe0000741900000000000093fe000075190000000000009dfe00007619000000000000a7fe00007719000000000000b1fe00007819000000000000bbfe00007919000000000000c5fe00007a19000000000000cffe00007b19000000000000d9fe00007c19000000000000e3fe00007d19000000000000edfe00007e19000000000000f7fe00007f1900000000000001ff000080190000000000000bff0000811900000000000015ff000082190000000000001fff0000831900000000000029ff0000841900000000000033ff000085190000000000003dff0000861900000000000047ff0000871900000000000051ff000088190000000000005bff0000891900000000000065ff00008a190000000000006fff00008b1900000000000079ff00008c1900000000000083ff00008d190000000000008dff00008e1900000000000097ff00008f19000000000000a1ff00009019000000000000abff00009119000000000000b5ff00009219000000000000bfff00009319000000000000c9ff00009419000000000000d3ff00009519000000000000ddff00009619000000000000e7ff00009719000000000000f1ff00009819000000000000fbff00009919000000000000050001009a190000000000000f0001009b19000000000000190001009c19000000000000230001009d190000000000002d0001009e19000000000000370001009f1900000000000041000100a0190000000000004b000100a11900000000000055000100a2190000000000005f000100a31900000000000069000100a41900000000000073000100a5190000000000007d000100a61900000000000087000100a71900000000000091000100a8190000000000009b000100a919000000000000a5000100aa19000000000000af000100ab19000000000000b9000100ac19000000000000c3000100ad19000000000000cd000100ae19000000000000d7000100af19000000000000e1000100b019000000000000eb000100b119000000000000f5000100b219000000000000ff000100b31900000000000009010100b41900000000000013010100b5190000000000001d010100b61900000000000027010100b71900000000000031010100b8190000000000003b010100b91900000000000045010100ba190000000000004f010100bb1900000000000059010100bc1900000000000063010100bd190000000000006d010100be1900000000000077010100bf1900000000000081010100c0190000000000008b010100c11900000000000095010100c2190000000000009f010100c319000000000000a9010100c419000000000000b3010100c519000000000000bd010100c619000000000000c7010100c719000000000000d1010100c819000000000000db010100c919000000000000e5010100ca19000000000000ef010100cb19000000000000f9010100cc1900000000000003020100cd190000000000000d020100ce1900000000000017020100cf1900000000000021020100d0190000000000002b020100d11900000000000035020100d2190000000000003f020100d31900000000000049020100d41900000000000053020100d5190000000000005d020100d61900000000000067020100d71900000000000071020100d8190000000000007b020100d91900000000000085020100da190000000000008f020100db1900000000000099020100dc19000000000000a3020100dd19000000000000ad020100de19000000000000b7020100df19000000000000c1020100e019000000000000cb020100e119000000000000d5020100e219000000000000df020100e319000000000000e9020100e419000000000000f3020100e519000000000000fd020100e61900000000000007030100e71900000000000011030100e8190000000000001b030100e91900000000000025030100ea190000000000002f030100eb1900000000000039030100ec1900000000000043030100ed190000000000004d030100ee1900000000000057030100ef1900000000000061030100f0190000000000006b030100f11900000000000075030100f2190000000000007f030100f31900000000000089030100f41900000000000093030100f5190000000000009d030100f619000000000000a7030100f719000000000000b1030100f819000000000000bb030100f919000000000000c5030100fa19000000000000cf030100fb19000000000000d9030100fc19000000000000e3030100fd19000000000000ed030100fe19000000000000f7030100ff1900000000000001040100001a0000000000000b040100011a00000000000015040100021a0000000000001f040100031a00000000000029040100041a00000000000033040100051a0000000000003d040100061a00000000000047040100071a00000000000051040100081a0000000000005b040100091a000000000000650401000a1a0000000000006f0401000b1a000000000000790401000c1a000000000000830401000d1a0000000000008d0401000e1a000000000000970401000f1a000000000000a1040100101a000000000000ab040100111a000000000000b5040100121a000000000000bf040100131a000000000000c9040100141a000000000000d3040100151a000000000000dd040100161a000000000000e7040100171a000000000000f1040100181a000000000000fb040100191a000000000000050501001a1a0000000000000f0501001b1a000000000000190501001c1a000000000000230501001d1a0000000000002d0501001e1a000000000000370501001f1a00000000000041050100201a0000000000004b050100211a00000000000055050100221a0000000000005f050100231a00000000000069050100241a00000000000073050100251a0000000000007d050100261a00000000000087050100271a00000000000091050100281a0000000000009b050100291a000000000000a50501002a1a000000000000af0501002b1a000000000000b90501002c1a000000000000c30501002d1a000000000000cd0501002e1a000000000000d70501002f1a000000000000e1050100301a000000000000eb050100311a000000000000f5050100321a000000000000ff050100331a00000000000009060100341a00000000000013060100351a0000000000001d060100361a00000000000027060100371a00000000000031060100381a0000000000003b060100391a000000000000450601003a1a0000000000004f0601003b1a000000000000590601003c1a000000000000630601003d1a0000000000006d0601003e1a000000000000770601003f1a00000000000081060100401a0000000000008b060100411a00000000000095060100421a0000000000009f060100431a000000000000a9060100441a000000000000b3060100451a000000000000bd060100461a000000000000c7060100471a000000000000d1060100481a000000000000db060100491a000000000000e50601004a1a000000000000ef0601004b1a000000000000f90601004c1a000000000000030701004d1a0000000000000d0701004e1a000000000000170701004f1a00000000000021070100501a0000000000002b070100511a00000000000035070100521a0000000000003f070100531a00000000000049070100541a00000000000053070100551a0000000000005d070100561a00000000000067070100571a00000000000071070100581a0000000000007b070100591a000000000000850701005a1a0000000000008f0701005b1a000000000000990701005c1a000000000000a30701005d1a000000000000ad0701005e1a000000000000b70701005f1a000000000000c1070100601a000000000000cb070100611a000000000000d5070100621a000000000000df070100631a000000000000e9070100641a000000000000f3070100651a000000000000fd070100661a00000000000007080100671a00000000000011080100681a0000000000001b080100691a000000000000250801006a1a0000000000002f0801006b1a000000000000390801006c1a000000000000430801006d1a0000000000004d0801006e1a000000000000570801006f1a00000000000061080100701a0000000000006b080100711a00000000000075080100721a0000000000007f080100731a00000000000089080100741a00000000000093080100751a0000000000009d080100761a000000000000a7080100771a000000000000b1080100781a000000000000bb080100791a000000000000c50801007a1a000000000000cf0801007b1a000000000000d90801007c1a000000000000e30801007d1a000000000000ed0801007e1a000000000000f70801007f1a00000000000001090100801a0000000000000b090100811a00000000000015090100821a0000000000001f090100831a00000000000029090100841a00000000000033090100851a0000000000003d090100861a00000000000047090100871a00000000000051090100881a0000000000005b090100891a000000000000650901008a1a0000000000006f0901008b1a000000000000790901008c1a000000000000830901008d1a0000000000008d0901008e1a000000000000970901008f1a000000000000a1090100901a000000000000ab090100911a000000000000b5090100921a000000000000bf090100931a000000000000c9090100941a000000000000d3090100951a000000000000dd090100961a000000000000e7090100971a000000000000f1090100981a000000000000fb090100991a000000000000050a01009a1a0000000000000f0a01009b1a000000000000190a01009c1a000000000000230a01009d1a0000000000002d0a01009e1a000000000000370a01009f1a000000000000410a0100a01a0000000000004b0a0100a11a000000000000550a0100a21a0000000000005f0a0100a31a000000000000690a0100a41a000000000000730a0100a51a0000000000007d0a0100a61a000000000000870a0100a71a000000000000910a0100a81a0000000000009b0a0100a91a000000000000a50a0100aa1a000000000000af0a0100ab1a000000000000b90a0100ac1a000000000000c30a0100ad1a000000000000cd0a0100ae1a000000000000d70a0100af1a000000000000e10a0100b01a000000000000eb0a0100b11a000000000000f50a0100b21a000000000000ff0a0100b31a000000000000090b0100b41a000000000000130b0100b51a0000000000001d0b0100b61a000000000000270b0100b71a000000000000310b0100b81a0000000000003b0b0100b91a000000000000450b0100ba1a0000000000004f0b0100bb1a000000000000590b0100bc1a000000000000630b0100bd1a0000000000006d0b0100be1a000000000000770b0100bf1a000000000000810b0100c01a0000000000008b0b0100c11a000000000000950b0100c21a0000000000009f0b0100c31a000000000000a90b0100c41a000000000000b30b0100c51a000000000000bd0b0100c61a000000000000c70b0100c71a000000000000d10b0100c81a000000000000db0b0100c91a000000000000e50b0100ca1a000000000000ef0b0100cb1a000000000000f90b0100cc1a000000000000030c0100cd1a0000000000000d0c0100ce1a000000000000170c0100cf1a000000000000210c0100d01a0000000000002b0c0100d11a000000000000350c0100d21a0000000000003f0c0100d31a000000000000490c0100d41a000000000000530c0100d51a0000000000005d0c0100d61a000000000000670c0100d71a000000000000710c0100d81a0000000000007b0c0100d91a000000000000850c0100da1a0000000000008f0c0100db1a000000000000990c0100dc1a000000000000a30c0100dd1a000000000000ad0c0100de1a000000000000b70c0100df1a000000000000c10c0100e01a000000000000cb0c0100e11a000000000000d50c0100e21a000000000000df0c0100e31a000000000000e90c0100e41a000000000000f30c0100e51a000000000000fd0c0100e61a000000000000070d0100e71a000000000000110d0100e81a0000000000001b0d0100e91a000000000000250d0100ea1a0000000000002f0d0100eb1a000000000000390d0100ec1a000000000000430d0100ed1a0000000000004d0d0100ee1a000000000000570d0100ef1a000000000000610d0100f01a0000000000006b0d0100f11a000000000000750d0100f21a0000000000007f0d0100f31a000000000000890d0100f41a000000000000930d0100f51a0000000000009d0d0100f61a000000000000a70d0100f71a000000000000b10d0100f81a000000000000bb0d0100f91a000000000000c50d0100fa1a000000000000cf0d0100fb1a000000000000d90d0100fc1a000000000000e30d0100fd1a000000000000ed0d0100fe1a000000000000f70d0100ff1a000000000000010e0100001b0000000000000b0e0100011b000000000000150e0100021b0000000000001f0e0100031b000000000000290e0100041b000000000000330e0100051b0000000000003d0e0100061b000000000000470e0100071b000000000000510e0100081b0000000000005b0e0100091b000000000000650e01000a1b0000000000006f0e01000b1b000000000000790e01000c1b000000000000830e01000d1b0000000000008d0e01000e1b000000000000970e01000f1b000000000000a10e0100101b000000000000ab0e0100111b000000000000b50e0100121b000000000000bf0e0100131b000000000000c90e0100141b000000000000d30e0100151b000000000000dd0e0100161b000000000000e70e0100171b000000000000f10e0100181b000000000000fb0e0100191b000000000000050f01001a1b0000000000000f0f01001b1b000000000000190f01001c1b000000000000230f01001d1b0000000000002d0f01001e1b000000000000370f01001f1b000000000000410f0100201b0000000000004b0f0100211b000000000000550f0100221b0000000000005f0f0100231b000000000000690f0100241b000000000000730f0100251b0000000000007d0f0100261b000000000000870f0100271b000000000000910f0100281b0000000000009b0f0100291b000000000000a50f01002a1b000000000000af0f01002b1b000000000000b90f01002c1b000000000000c30f01002d1b000000000000cd0f01002e1b000000000000d70f01002f1b000000000000e10f0100301b000000000000eb0f0100311b000000000000f50f0100321b000000000000ff0f0100331b00000000000009100100341b00000000000013100100351b0000000000001d100100361b00000000000027100100371b00000000000031100100381b0000000000003b100100391b000000000000451001003a1b0000000000004f1001003b1b000000000000591001003c1b000000000000631001003d1b0000000000006d1001003e1b000000000000771001003f1b00000000000081100100401b0000000000008b100100411b00000000000095100100421b0000000000009f100100431b000000000000a9100100441b000000000000b3100100451b000000000000bd100100461b000000000000c7100100471b000000000000d1100100481b000000000000db100100491b000000000000e51001004a1b000000000000ef1001004b1b000000000000f91001004c1b000000000000031101004d1b0000000000000d1101004e1b000000000000171101004f1b00000000000021110100501b0000000000002b110100511b00000000000035110100521b0000000000003f110100531b00000000000049110100541b00000000000053110100551b0000000000005d110100561b00000000000067110100571b00000000000071110100581b0000000000007b110100591b000000000000851101005a1b0000000000008f1101005b1b000000000000991101005c1b000000000000a31101005d1b000000000000ad1101005e1b000000000000b71101005f1b000000000000c1110100601b000000000000cb110100611b000000000000d5110100621b000000000000df110100631b000000000000e9110100641b000000000000f3110100651b000000000000fd110100661b00000000000007120100671b00000000000011120100681b0000000000001b120100691b000000000000251201006a1b0000000000002f1201006b1b000000000000391201006c1b000000000000431201006d1b0000000000004d1201006e1b000000000000571201006f1b00000000000061120100701b0000000000006b120100711b00000000000075120100721b0000000000007f120100731b00000000000089120100741b00000000000093120100751b0000000000009d120100761b000000000000a7120100771b000000000000b1120100781b000000000000bb120100791b000000000000c51201007a1b000000000000cf1201007b1b000000000000d91201007c1b000000000000e31201007d1b000000000000ed1201007e1b000000000000f71201007f1b00000000000001130100801b0000000000000b130100811b00000000000015130100821b0000000000001f130100831b00000000000029130100841b00000000000033130100851b0000000000003d130100861b00000000000047130100871b00000000000051130100881b0000000000005b130100891b000000000000651301008a1b0000000000006f1301008b1b000000000000791301008c1b000000000000831301008d1b0000000000008d1301008e1b000000000000971301008f1b000000000000a1130100901b000000000000ab130100911b000000000000b5130100921b000000000000bf130100931b000000000000c9130100941b000000000000d3130100951b000000000000dd130100961b000000000000e7130100971b000000000000f1130100981b000000000000fb130100991b000000000000051401009a1b0000000000000f1401009b1b000000000000191401009c1b000000000000231401009d1b0000000000002d1401009e1b000000000000371401009f1b00000000000041140100a01b0000000000004b140100a11b00000000000055140100a21b0000000000005f140100a31b00000000000069140100a41b00000000000073140100a51b0000000000007d140100a61b00000000000087140100a71b00000000000091140100a81b0000000000009b140100a91b000000000000a5140100aa1b000000000000af140100ab1b000000000000b9140100ac1b000000000000c3140100ad1b000000000000cd140100ae1b000000000000d7140100af1b000000000000e1140100b01b000000000000eb140100b11b000000000000f5140100b21b000000000000ff140100b31b00000000000009150100b41b00000000000013150100b51b0000000000001d150100b61b00000000000027150100b71b00000000000031150100b81b0000000000003b150100b91b00000000000045150100ba1b0000000000004f150100bb1b00000000000059150100bc1b00000000000063150100bd1b0000000000006d150100be1b00000000000077150100bf1b00000000000081150100c01b0000000000008b150100c11b00000000000095150100c21b0000000000009f150100c31b000000000000a9150100c41b000000000000b3150100c51b000000000000bd150100c61b000000000000c7150100c71b000000000000d1150100c81b000000000000db150100c91b000000000000e5150100ca1b000000000000ef150100cb1b000000000000f9150100cc1b00000000000003160100cd1b0000000000000d160100ce1b00000000000017160100cf1b00000000000021160100d01b0000000000002b160100d11b00000000000035160100d21b0000000000003f160100d31b00000000000049160100d41b00000000000053160100d51b0000000000005d160100d61b00000000000067160100d71b00000000000071160100d81b0000000000007b160100d91b00000000000085160100da1b0000000000008f160100db1b00000000000099160100dc1b000000000000a3160100dd1b000000000000ad160100de1b000000000000b7160100df1b000000000000c1160100e01b000000000000cb160100e11b000000000000d5160100e21b000000000000df160100e31b000000000000e9160100e41b000000000000f3160100e51b000000000000fd160100e61b00000000000007170100e71b00000000000011170100e81b0000000000001b170100e91b00000000000025170100ea1b0000000000002f170100eb1b00000000000039170100ec1b00000000000043170100ed1b0000000000004d170100ee1b00000000000057170100ef1b00000000000061170100f01b0000000000006b170100f11b00000000000075170100f21b0000000000007f170100f31b00000000000089170100f41b00000000000093170100f51b0000000000009d170100f61b000000000000a7170100f71b000000000000b1170100f81b000000000000bb170100f91b000000000000c5170100fa1b000000000000cf170100fb1b000000000000d9170100fc1b000000000000e3170100fd1b000000000000ed170100fe1b000000000000f7170100ff1b00000000000001180100001c0000000000000b180100011c00000000000015180100021c0000000000001f180100031c00000000000029180100041c00000000000033180100051c0000000000003d180100061c00000000000047180100071c00000000000051180100081c0000000000005b180100091c000000000000651801000a1c0000000000006f1801000b1c000000000000791801000c1c000000000000831801000d1c0000000000008d1801000e1c000000000000971801000f1c000000000000a1180100101c000000000000ab180100111c000000000000b5180100121c000000000000bf180100131c000000000000c9180100141c000000000000d3180100151c000000000000dd180100161c000000000000e7180100171c000000000000f1180100181c000000000000fb180100191c000000000000051901001a1c0000000000000f1901001b1c000000000000191901001c1c000000000000231901001d1c0000000000002d1901001e1c000000000000371901001f1c00000000000041190100201c0000000000004b190100211c00000000000055190100221c0000000000005f190100231c00000000000069190100241c00000000000073190100251c0000000000007d190100261c00000000000087190100271c00000000000091190100281c0000000000009b190100291c000000000000a51901002a1c000000000000af1901002b1c000000000000b91901002c1c000000000000c31901002d1c000000000000cd1901002e1c000000000000d71901002f1c000000000000e1190100301c000000000000eb190100311c000000000000f5190100321c000000000000ff190100331c000000000000091a0100341c000000000000131a0100351c0000000000001d1a0100361c000000000000271a0100371c000000000000311a0100381c0000000000003b1a0100391c000000000000451a01003a1c0000000000004f1a01003b1c000000000000591a01003c1c000000000000631a01003d1c0000000000006d1a01003e1c000000000000771a01003f1c000000000000811a0100401c0000000000008b1a0100411c000000000000951a0100421c0000000000009f1a0100431c000000000000a91a0100441c000000000000b31a0100451c000000000000bd1a0100461c000000000000c71a0100471c000000000000d11a0100481c000000000000db1a0100491c000000000000e51a01004a1c000000000000ef1a01004b1c000000000000f91a01004c1c000000000000031b01004d1c0000000000000d1b01004e1c000000000000171b01004f1c000000000000211b0100501c0000000000002b1b0100511c000000000000351b0100521c0000000000003f1b0100531c000000000000491b0100541c000000000000531b0100551c0000000000005d1b0100561c000000000000671b0100571c000000000000711b0100581c0000000000007b1b0100591c000000000000851b01005a1c0000000000008f1b01005b1c000000000000991b01005c1c000000000000a31b01005d1c000000000000ad1b01005e1c000000000000b71b01005f1c000000000000c11b0100601c000000000000cb1b0100611c000000000000d51b0100621c000000000000df1b0100631c000000000000e91b0100641c000000000000f31b0100651c000000000000fd1b0100661c000000000000071c0100671c000000000000111c0100681c0000000000001b1c0100691c000000000000251c01006a1c0000000000002f1c01006b1c000000000000391c01006c1c000000000000431c01006d1c0000000000004d1c01006e1c000000000000571c01006f1c000000000000611c0100701c0000000000006b1c0100711c000000000000751c0100721c0000000000007f1c0100731c000000000000891c0100741c000000000000931c0100751c0000000000009d1c0100761c000000000000a71c0100771c000000000000b11c0100781c000000000000bb1c0100791c000000000000c51c01007a1c000000000000cf1c01007b1c000000000000d91c01007c1c000000000000e31c01007d1c000000000000ed1c01007e1c000000000000f71c01007f1c000000000000011d0100801c0000000000000b1d0100811c000000000000151d0100821c0000000000001f1d0100831c000000000000291d0100841c000000000000331d0100851c0000000000003d1d0100861c000000000000471d0100871c000000000000511d0100881c0000000000005b1d0100891c000000000000651d01008a1c0000000000006f1d01008b1c000000000000791d01008c1c000000000000831d01008d1c0000000000008d1d01008e1c000000000000971d01008f1c000000000000a11d0100901c000000000000ab1d0100911c000000000000b51d0100921c000000000000bf1d0100931c000000000000c91d0100941c000000000000d31d0100951c000000000000dd1d0100961c000000000000e71d0100971c000000000000f11d0100981c000000000000fb1d0100991c000000000000051e01009a1c0000000000000f1e01009b1c000000000000191e01009c1c000000000000231e01009d1c0000000000002d1e01009e1c000000000000371e01009f1c000000000000411e0100a01c0000000000004b1e0100a11c000000000000551e0100a21c0000000000005f1e0100a31c000000000000691e0100a41c000000000000731e0100a51c0000000000007d1e0100a61c000000000000871e0100a71c000000000000911e0100a81c0000000000009b1e0100a91c000000000000a51e0100aa1c000000000000af1e0100ab1c000000000000b91e0100ac1c000000000000c31e0100ad1c000000000000cd1e0100ae1c000000000000d71e0100af1c000000000000e11e0100b01c000000000000eb1e0100b11c000000000000f51e0100b21c000000000000ff1e0100b31c000000000000091f0100b41c000000000000131f0100b51c0000000000001d1f0100b61c000000000000271f0100b71c000000000000311f0100b81c0000000000003b1f0100b91c000000000000451f0100ba1c0000000000004f1f0100bb1c000000000000591f0100bc1c000000000000631f0100bd1c0000000000006d1f0100be1c000000000000771f0100bf1c000000000000811f0100c01c0000000000008b1f0100c11c000000000000951f0100c21c0000000000009f1f0100c31c000000000000a91f0100c41c000000000000b31f0100c51c000000000000bd1f0100c61c000000000000c71f0100c71c000000000000d11f0100c81c000000000000db1f0100c91c000000000000e51f0100ca1c000000000000ef1f0100cb1c000000000000f91f0100cc1c00000000000003200100cd1c0000000000000d200100ce1c00000000000017200100cf1c00000000000021200100d01c0000000000002b200100d11c00000000000035200100d21c0000000000003f200100d31c00000000000049200100d41c00000000000053200100d51c0000000000005d200100d61c00000000000067200100d71c00000000000071200100d81c0000000000007b200100d91c00000000000085200100da1c0000000000008f200100db1c00000000000099200100dc1c000000000000a3200100dd1c000000000000ad200100de1c000000000000b7200100df1c000000000000c1200100e01c000000000000cb200100e11c000000000000d5200100e21c000000000000df200100e31c000000000000e9200100e41c000000000000f3200100e51c000000000000fd200100e61c00000000000007210100e71c00000000000011210100e81c0000000000001b210100e91c00000000000025210100ea1c0000000000002f210100eb1c00000000000039210100ec1c00000000000043210100ed1c0000000000004d210100ee1c00000000000057210100ef1c00000000000061210100f01c0000000000006b210100f11c00000000000075210100f21c0000000000007f210100f31c00000000000089210100f41c00000000000093210100f51c0000000000009d210100f61c000000000000a7210100f71c000000000000b1210100f81c000000000000bb210100f91c000000000000c5210100fa1c000000000000cf210100fb1c000000000000d9210100fc1c000000000000e3210100fd1c000000000000ed210100fe1c000000000000f7210100ff1c00000000000001220100001d0000000000000b220100011d00000000000015220100021d0000000000001f220100031d00000000000029220100041d00000000000033220100051d0000000000003d220100061d00000000000047220100071d00000000000051220100081d0000000000005b220100091d000000000000652201000a1d0000000000006f2201000b1d000000000000792201000c1d000000000000832201000d1d0000000000008d2201000e1d000000000000972201000f1d000000000000a1220100101d000000000000ab220100111d000000000000b5220100121d000000000000bf220100131d000000000000c9220100141d000000000000d3220100151d000000000000dd220100161d000000000000e7220100171d000000000000f1220100181d000000000000fb220100191d000000000000052301001a1d0000000000000f2301001b1d000000000000192301001c1d000000000000232301001d1d0000000000002d2301001e1d000000000000372301001f1d00000000000041230100201d0000000000004b230100211d00000000000055230100221d0000000000005f230100231d00000000000069230100241d00000000000073230100251d0000000000007d230100261d00000000000087230100271d00000000000091230100281d0000000000009b230100291d000000000000a52301002a1d000000000000af2301002b1d000000000000b92301002c1d000000000000c32301002d1d000000000000cd2301002e1d000000000000d72301002f1d000000000000e1230100301d000000000000eb230100311d000000000000f5230100321d000000000000ff230100331d00000000000009240100341d00000000000013240100351d0000000000001d240100361d00000000000027240100371d00000000000031240100381d0000000000003b240100391d000000000000452401003a1d0000000000004f2401003b1d000000000000592401003c1d000000000000632401003d1d0000000000006d2401003e1d000000000000772401003f1d00000000000081240100401d0000000000008b240100411d00000000000095240100421d0000000000009f240100431d000000000000a9240100441d000000000000b3240100451d000000000000bd240100461d000000000000c7240100471d000000000000d1240100481d000000000000db240100491d000000000000e52401004a1d000000000000ef2401004b1d000000000000f92401004c1d000000000000032501004d1d0000000000000d2501004e1d000000000000172501004f1d00000000000021250100501d0000000000002b250100511d00000000000035250100521d0000000000003f250100531d00000000000049250100541d00000000000053250100551d0000000000005d250100561d00000000000067250100571d00000000000071250100581d0000000000007b250100591d000000000000852501005a1d0000000000008f2501005b1d000000000000992501005c1d000000000000a32501005d1d000000000000ad2501005e1d000000000000b72501005f1d000000000000c1250100601d000000000000cb250100611d000000000000d5250100621d000000000000df250100631d000000000000e9250100641d000000000000f3250100651d000000000000fd250100661d00000000000007260100671d00000000000011260100681d0000000000001b260100691d000000000000252601006a1d0000000000002f2601006b1d000000000000392601006c1d000000000000432601006d1d0000000000004d2601006e1d000000000000572601006f1d00000000000061260100701d0000000000006b260100711d00000000000075260100721d0000000000007f260100731d00000000000089260100741d00000000000093260100751d0000000000009d260100761d000000000000a7260100771d000000000000b1260100781d000000000000bb260100791d000000000000c52601007a1d000000000000cf2601007b1d000000000000d92601007c1d000000000000e32601007d1d000000000000ed2601007e1d000000000000f72601007f1d00000000000001270100801d0000000000000b270100811d00000000000015270100821d0000000000001f270100831d00000000000029270100841d00000000000033270100851d0000000000003d270100861d00000000000047270100871d00000000000051270100881d0000000000005b270100891d000000000000652701008a1d0000000000006f2701008b1d000000000000792701008c1d000000000000832701008d1d0000000000008d2701008e1d000000000000972701008f1d000000000000a1270100901d000000000000ab270100911d000000000000b5270100921d000000000000bf270100931d000000000000c9270100941d000000000000d3270100951d000000000000dd270100961d000000000000e7270100971d000000000000f1270100981d000000000000fb270100991d000000000000052801009a1d0000000000000f2801009b1d000000000000192801009c1d000000000000232801009d1d0000000000002d2801009e1d000000000000372801009f1d00000000000041280100a01d0000000000004b280100a11d00000000000055280100a21d0000000000005f280100a31d00000000000069280100a41d00000000000073280100a51d0000000000007d280100a61d00000000000087280100a71d00000000000091280100a81d0000000000009b280100a91d000000000000a5280100aa1d000000000000af280100ab1d000000000000b9280100ac1d000000000000c3280100ad1d000000000000cd280100ae1d000000000000d7280100af1d000000000000e1280100b01d000000000000eb280100b11d000000000000f5280100b21d000000000000ff280100b31d00000000000009290100b41d00000000000013290100b51d0000000000001d290100b61d00000000000027290100b71d00000000000031290100b81d0000000000003b290100b91d00000000000045290100ba1d0000000000004f290100bb1d00000000000059290100bc1d00000000000063290100bd1d0000000000006d290100be1d00000000000077290100bf1d00000000000081290100c01d0000000000008b290100c11d00000000000095290100c21d0000000000009f290100c31d000000000000a9290100c41d000000000000b3290100c51d000000000000bd290100c61d000000000000c7290100c71d000000000000d1290100c81d000000000000db290100c91d000000000000e5290100ca1d000000000000ef290100cb1d000000000000f9290100cc1d000000000000032a0100cd1d0000000000000d2a0100ce1d000000000000172a0100cf1d000000000000212a0100d01d0000000000002b2a0100d11d000000000000352a0100d21d0000000000003f2a0100d31d000000000000492a0100d41d000000000000532a0100d51d0000000000005d2a0100d61d000000000000672a0100d71d000000000000712a0100d81d0000000000007b2a0100d91d000000000000852a0100da1d0000000000008f2a0100db1d000000000000992a0100dc1d000000000000a32a0100dd1d000000000000ad2a0100de1d000000000000b72a0100df1d000000000000c12a0100e01d000000000000cb2a0100e11d000000000000d52a0100e21d000000000000df2a0100e31d000000000000e92a0100e41d000000000000f32a0100e51d000000000000fd2a0100e61d000000000000072b0100e71d000000000000112b0100e81d0000000000001b2b0100e91d000000000000252b0100ea1d0000000000002f2b0100eb1d000000000000392b0100ec1d000000000000432b0100ed1d0000000000004d2b0100ee1d000000000000572b0100ef1d000000000000612b0100f01d0000000000006b2b0100f11d000000000000752b0100f21d0000000000007f2b0100f31d000000000000892b0100f41d000000000000932b0100f51d0000000000009d2b0100f61d000000000000a72b0100f71d000000000000b12b0100f81d000000000000bb2b0100f91d000000000000c52b0100fa1d000000000000cf2b0100fb1d000000000000d92b0100fc1d000000000000e32b0100fd1d000000000000ed2b0100fe1d000000000000f72b0100ff1d000000000000012c0100001e0000000000000b2c0100011e000000000000152c0100021e0000000000001f2c0100031e000000000000292c0100041e000000000000332c0100051e0000000000003d2c0100061e000000000000472c0100071e000000000000512c0100081e0000000000005b2c0100091e000000000000652c01000a1e0000000000006f2c01000b1e000000000000792c01000c1e000000000000832c01000d1e0000000000008d2c01000e1e000000000000972c01000f1e000000000000a12c0100101e000000000000ab2c0100111e000000000000b52c0100121e000000000000bf2c0100131e000000000000c92c0100141e000000000000d32c0100151e000000000000dd2c0100161e000000000000e72c0100171e000000000000f12c0100181e000000000000fb2c0100191e000000000000052d01001a1e0000000000000f2d01001b1e000000000000192d01001c1e000000000000232d01001d1e0000000000002d2d01001e1e000000000000372d01001f1e000000000000412d0100201e0000000000004b2d0100211e000000000000552d0100221e0000000000005f2d0100231e000000000000692d0100241e000000000000732d0100251e0000000000007d2d0100261e000000000000872d0100271e000000000000912d0100281e0000000000009b2d0100291e000000000000a52d01002a1e000000000000af2d01002b1e000000000000b92d01002c1e000000000000c32d01002d1e000000000000cd2d01002e1e000000000000d72d01002f1e000000000000e12d0100301e000000000000eb2d0100311e000000000000f52d0100321e000000000000ff2d0100331e000000000000092e0100341e000000000000132e0100351e0000000000001d2e0100361e000000000000272e0100371e000000000000312e0100381e0000000000003b2e0100391e000000000000452e01003a1e0000000000004f2e01003b1e000000000000592e01003c1e000000000000632e01003d1e0000000000006d2e01003e1e000000000000772e01003f1e000000000000812e0100401e0000000000008b2e0100411e000000000000952e0100421e0000000000009f2e0100431e000000000000a92e0100441e000000000000b32e0100451e000000000000bd2e0100461e000000000000c72e0100471e000000000000d12e0100481e000000000000db2e0100491e000000000000e52e01004a1e000000000000ef2e01004b1e000000000000f92e01004c1e000000000000032f01004d1e0000000000000d2f01004e1e000000000000172f01004f1e000000000000212f0100501e0000000000002b2f0100511e000000000000352f0100521e0000000000003f2f0100531e000000000000492f0100541e000000000000532f0100551e0000000000005d2f0100561e000000000000672f0100571e000000000000712f0100581e0000000000007b2f0100591e000000000000852f01005a1e0000000000008f2f01005b1e000000000000992f01005c1e000000000000a32f01005d1e000000000000ad2f01005e1e000000000000b72f01005f1e000000000000c12f0100601e000000000000cb2f0100611e000000000000d52f0100621e000000000000df2f0100631e000000000000e92f0100641e000000000000f32f0100651e000000000000fd2f0100661e00000000000007300100671e00000000000011300100681e0000000000001b300100691e000000000000253001006a1e0000000000002f3001006b1e000000000000393001006c1e000000000000433001006d1e0000000000004d3001006e1e000000000000573001006f1e00000000000061300100701e0000000000006b300100711e00000000000075300100721e0000000000007f300100731e00000000000089300100741e00000000000093300100751e0000000000009d300100761e000000000000a7300100771e000000000000b1300100781e000000000000bb300100791e000000000000c53001007a1e000000000000cf3001007b1e000000000000d93001007c1e000000000000e33001007d1e000000000000ed3001007e1e000000000000f73001007f1e00000000000001310100801e0000000000000b310100811e00000000000015310100821e0000000000001f310100831e00000000000029310100841e00000000000033310100851e0000000000003d310100861e00000000000047310100871e00000000000051310100881e0000000000005b310100891e000000000000653101008a1e0000000000006f3101008b1e000000000000793101008c1e000000000000833101008d1e0000000000008d3101008e1e000000000000973101008f1e000000000000a1310100901e000000000000ab310100911e000000000000b5310100921e000000000000bf310100931e000000000000c9310100941e000000000000d3310100951e000000000000dd310100961e000000000000e7310100971e000000000000f1310100981e000000000000fb310100991e000000000000053201009a1e0000000000000f3201009b1e000000000000193201009c1e000000000000233201009d1e0000000000002d3201009e1e000000000000373201009f1e00000000000041320100a01e0000000000004b320100a11e00000000000055320100a21e0000000000005f320100a31e00000000000069320100a41e00000000000073320100a51e0000000000007d320100a61e00000000000087320100a71e00000000000091320100a81e0000000000009b320100a91e000000000000a5320100aa1e000000000000af320100ab1e000000000000b9320100ac1e000000000000c3320100ad1e000000000000cd320100ae1e000000000000d7320100af1e000000000000e1320100b01e000000000000eb320100b11e000000000000f5320100b21e000000000000ff320100b31e00000000000009330100b41e00000000000013330100b51e0000000000001d330100b61e00000000000027330100b71e00000000000031330100b81e0000000000003b330100b91e00000000000045330100ba1e0000000000004f330100bb1e00000000000059330100bc1e00000000000063330100bd1e0000000000006d330100be1e00000000000077330100bf1e00000000000081330100c01e0000000000008b330100c11e00000000000095330100c21e0000000000009f330100c31e000000000000a9330100c41e000000000000b3330100c51e000000000000bd330100c61e000000000000c7330100c71e000000000000d1330100c81e000000000000db330100c91e000000000000e5330100ca1e000000000000ef330100cb1e000000000000f9330100cc1e00000000000003340100cd1e0000000000000d340100ce1e00000000000017340100cf1e00000000000021340100d01e0000000000002b340100d11e00000000000035340100d21e0000000000003f340100d31e00000000000049340100d41e00000000000053340100d51e0000000000005d340100d61e00000000000067340100d71e00000000000071340100d81e0000000000007b340100d91e00000000000085340100da1e0000000000008f340100db1e00000000000099340100dc1e000000000000a3340100dd1e000000000000ad340100de1e000000000000b7340100df1e000000000000c1340100e01e000000000000cb340100e11e000000000000d5340100e21e000000000000df340100e31e000000000000e9340100e41e000000000000f3340100e51e000000000000fd340100e61e00000000000007350100e71e00000000000011350100e81e0000000000001b350100e91e00000000000025350100ea1e0000000000002f350100eb1e00000000000039350100ec1e00000000000043350100ed1e0000000000004d350100ee1e00000000000057350100ef1e00000000000061350100f01e0000000000006b350100f11e00000000000075350100f21e0000000000007f350100f31e00000000000089350100f41e00000000000093350100f51e0000000000009d350100f61e000000000000a7350100f71e000000000000b1350100f81e000000000000bb350100f91e000000000000c5350100fa1e000000000000cf350100fb1e000000000000d9350100fc1e000000000000e3350100fd1e000000000000ed350100fe1e000000000000f7350100ff1e00000000000001360100001f0000000000000b360100011f00000000000015360100021f0000000000001f360100031f00000000000029360100041f00000000000033360100051f0000000000003d360100061f00000000000047360100071f00000000000051360100081f0000000000005b360100091f000000000000653601000a1f0000000000006f3601000b1f000000000000793601000c1f000000000000833601000d1f0000000000008d3601000e1f000000000000973601000f1f000000000000a1360100101f000000000000ab360100111f000000000000b5360100121f000000000000bf360100131f000000000000c9360100141f000000000000d3360100151f000000000000dd360100161f000000000000e7360100171f000000000000f1360100181f000000000000fb360100191f000000000000053701001a1f0000000000000f3701001b1f000000000000193701001c1f000000000000233701001d1f0000000000002d3701001e1f000000000000373701001f1f00000000000041370100201f0000000000004b370100211f00000000000055370100221f0000000000005f370100231f00000000000069370100241f00000000000073370100251f0000000000007d370100261f00000000000087370100271f00000000000091370100281f0000000000009b370100291f000000000000a53701002a1f000000000000af3701002b1f000000000000b93701002c1f000000000000c33701002d1f000000000000cd3701002e1f000000000000d73701002f1f000000000000e1370100301f000000000000eb370100311f000000000000f5370100321f000000000000ff370100331f00000000000009380100341f00000000000013380100351f0000000000001d380100361f00000000000027380100371f00000000000031380100381f0000000000003b380100391f000000000000453801003a1f0000000000004f3801003b1f000000000000593801003c1f000000000000633801003d1f0000000000006d3801003e1f000000000000773801003f1f00000000000081380100401f0000000000008b380100411f00000000000095380100421f0000000000009f380100431f000000000000a9380100441f000000000000b3380100451f000000000000bd380100461f000000000000c7380100471f000000000000d1380100481f000000000000db380100491f000000000000e53801004a1f000000000000ef3801004b1f000000000000f93801004c1f000000000000033901004d1f0000000000000d3901004e1f000000000000173901004f1f00000000000021390100501f0000000000002b390100511f00000000000035390100521f0000000000003f390100531f00000000000049390100541f00000000000053390100551f0000000000005d390100561f00000000000067390100571f00000000000071390100581f0000000000007b390100591f000000000000853901005a1f0000000000008f3901005b1f000000000000993901005c1f000000000000a33901005d1f000000000000ad3901005e1f000000000000b73901005f1f000000000000c1390100601f000000000000cb390100611f000000000000d5390100621f000000000000df390100631f000000000000e9390100641f000000000000f3390100651f000000000000fd390100661f000000000000073a0100671f000000000000113a0100681f0000000000001b3a0100691f000000000000253a01006a1f0000000000002f3a01006b1f000000000000393a01006c1f000000000000433a01006d1f0000000000004d3a01006e1f000000000000573a01006f1f000000000000613a0100701f0000000000006b3a0100711f000000000000753a0100721f0000000000007f3a0100731f000000000000893a0100741f000000000000933a0100751f0000000000009d3a0100761f000000000000a73a0100771f000000000000b13a0100781f000000000000bb3a0100791f000000000000c53a01007a1f000000000000cf3a01007b1f000000000000d93a01007c1f000000000000e33a01007d1f000000000000ed3a01007e1f000000000000f73a01007f1f000000000000013b0100801f0000000000000b3b0100811f000000000000153b0100821f0000000000001f3b0100831f000000000000293b0100841f000000000000333b0100851f0000000000003d3b0100861f000000000000473b0100871f000000000000513b0100881f0000000000005b3b0100891f000000000000653b01008a1f0000000000006f3b01008b1f000000000000793b01008c1f000000000000833b01008d1f0000000000008d3b01008e1f000000000000973b01008f1f000000000000a13b0100901f000000000000ab3b0100911f000000000000b53b0100921f000000000000bf3b0100931f000000000000c93b0100941f000000000000d33b0100951f000000000000dd3b0100961f000000000000e73b0100971f000000000000f13b0100981f000000000000fb3b0100991f000000000000053c01009a1f0000000000000f3c01009b1f000000000000193c01009c1f000000000000233c01009d1f0000000000002d3c01009e1f000000000000373c01009f1f000000000000413c0100a01f0000000000004b3c0100a11f000000000000553c0100a21f0000000000005f3c0100a31f000000000000693c0100a41f000000000000733c0100a51f0000000000007d3c0100a61f000000000000873c0100a71f000000000000913c0100a81f0000000000009b3c0100a91f000000000000a53c0100aa1f000000000000af3c0100ab1f000000000000b93c0100ac1f000000000000c33c0100ad1f000000000000cd3c0100ae1f000000000000d73c0100af1f000000000000e13c0100b01f000000000000eb3c0100b11f000000000000f53c0100b21f000000000000ff3c0100b31f000000000000093d0100b41f000000000000133d0100b51f0000000000001d3d0100b61f000000000000273d0100b71f000000000000313d0100b81f0000000000003b3d0100b91f000000000000453d0100ba1f0000000000004f3d0100bb1f000000000000593d0100bc1f000000000000633d0100bd1f0000000000006d3d0100be1f000000000000773d0100bf1f000000000000813d0100c01f0000000000008b3d0100c11f000000000000953d0100c21f0000000000009f3d0100c31f000000000000a93d0100c41f000000000000b33d0100c51f000000000000bd3d0100c61f000000000000c73d0100c71f000000000000d13d0100c81f000000000000db3d0100c91f000000000000e53d0100ca1f000000000000ef3d0100cb1f000000000000f93d0100cc1f000000000000033e0100cd1f0000000000000d3e0100ce1f000000000000173e0100cf1f000000000000213e0100d01f0000000000002b3e0100d11f000000000000353e0100d21f0000000000003f3e0100d31f000000000000493e0100d41f000000000000533e0100d51f0000000000005d3e0100d61f000000000000673e0100d71f000000000000713e0100d81f0000000000007b3e0100d91f000000000000853e0100da1f0000000000008f3e0100db1f000000000000993e0100dc1f000000000000a33e0100dd1f000000000000ad3e0100de1f000000000000b73e0100df1f000000000000c13e0100e01f000000000000cb3e0100e11f000000000000d53e0100e21f000000000000df3e0100e31f000000000000e93e0100e41f000000000000f33e0100e51f000000000000fd3e0100e61f000000000000073f0100e71f000000000000113f0100e81f0000000000001b3f0100e91f000000000000253f0100ea1f0000000000002f3f0100eb1f000000000000393f0100ec1f000000000000433f0100ed1f0000000000004d3f0100ee1f000000000000573f0100ef1f000000000000613f0100f01f0000000000006b3f0100f11f000000000000753f0100f21f0000000000007f3f0100f31f000000000000893f0100f41f000000000000933f0100f51f0000000000009d3f0100f61f000000000000a73f0100f71f000000000000b13f0100f81f000000000000bb3f0100f91f000000000000c53f0100fa1f000000000000cf3f0100fb1f000000000000d93f0100fc1f000000000000e33f0100fd1f000000000000ed3f0100fe1f000000000000f73f0100ff1f0000000000000140010000200000000000000b40010001200000000000001540010002200000000000001f40010003200000000000002940010004200000000000003340010005200000000000003d40010006200000000000004740010007200000000000005140010008200000000000005b4001000920000000000000654001000a200000000000006f4001000b20000000000000794001000c20000000000000834001000d200000000000008d4001000e20000000000000974001000f20000000000000a14001001020000000000000ab4001001120000000000000b54001001220000000000000bf4001001320000000000000c94001001420000000000000d34001001520000000000000dd4001001620000000000000e74001001720000000000000f14001001820000000000000fb4001001920000000000000054101001a200000000000000f4101001b20000000000000194101001c20000000000000234101001d200000000000002d4101001e20000000000000374101001f200000000000004141010020200000000000004b41010021200000000000005541010022200000000000005f41010023200000000000006941010024200000000000007341010025200000000000007d41010026200000000000008741010027200000000000009141010028200000000000009b4101002920000000000000a54101002a20000000000000af4101002b20000000000000b94101002c20000000000000c34101002d20000000000000cd4101002e20000000000000d74101002f20000000000000e14101003020000000000000eb4101003120000000000000f54101003220000000000000ff41010033200000000000000942010034200000000000001342010035200000000000001d42010036200000000000002742010037200000000000003142010038200000000000003b4201003920000000000000454201003a200000000000004f4201003b20000000000000594201003c20000000000000634201003d200000000000006d4201003e20000000000000774201003f200000000000008142010040200000000000008b42010041200000000000009542010042200000000000009f4201004320000000000000a94201004420000000000000b34201004520000000000000bd4201004620000000000000c74201004720000000000000d14201004820000000000000db4201004920000000000000e54201004a20000000000000ef4201004b20000000000000f94201004c20000000000000034301004d200000000000000d4301004e20000000000000174301004f200000000000002143010050200000000000002b43010051200000000000003543010052200000000000003f43010053200000000000004943010054200000000000005343010055200000000000005d43010056200000000000006743010057200000000000007143010058200000000000007b4301005920000000000000854301005a200000000000008f4301005b20000000000000994301005c20000000000000a34301005d20000000000000ad4301005e20000000000000b74301005f20000000000000c14301006020000000000000cb4301006120000000000000d54301006220000000000000df4301006320000000000000e94301006420000000000000f34301006520000000000000fd43010066200000000000000744010067200000000000001144010068200000000000001b4401006920000000000000254401006a200000000000002f4401006b20000000000000394401006c20000000000000434401006d200000000000004d4401006e20000000000000574401006f200000000000006144010070200000000000006b44010071200000000000007544010072200000000000007f44010073200000000000008944010074200000000000009344010075200000000000009d4401007620000000000000a74401007720000000000000b14401007820000000000000bb4401007920000000000000c54401007a20000000000000cf4401007b20000000000000d94401007c20000000000000e34401007d20000000000000ed4401007e20000000000000f74401007f200000000000000145010080200000000000000b45010081200000000000001545010082200000000000001f45010083200000000000002945010084200000000000003345010085200000000000003d45010086200000000000004745010087200000000000005145010088200000000000005b4501008920000000000000654501008a200000000000006f4501008b20000000000000794501008c20000000000000834501008d200000000000008d4501008e20000000000000974501008f20000000000000a14501009020000000000000ab4501009120000000000000b54501009220000000000000bf4501009320000000000000c94501009420000000000000d34501009520000000000000dd4501009620000000000000e74501009720000000000000f14501009820000000000000fb4501009920000000000000054601009a200000000000000f4601009b20000000000000194601009c20000000000000234601009d200000000000002d4601009e20000000000000374601009f2000000000000041460100a0200000000000004b460100a12000000000000055460100a2200000000000005f460100a32000000000000069460100a42000000000000073460100a5200000000000007d460100a62000000000000087460100a72000000000000091460100a8200000000000009b460100a920000000000000a5460100aa20000000000000af460100ab20000000000000b9460100ac20000000000000c3460100ad20000000000000cd460100ae20000000000000d7460100af20000000000000e1460100b020000000000000eb460100b120000000000000f5460100b220000000000000ff460100b32000000000000009470100b42000000000000013470100b5200000000000001d470100b62000000000000027470100b72000000000000031470100b8200000000000003b470100b92000000000000045470100ba200000000000004f470100bb2000000000000059470100bc2000000000000063470100bd200000000000006d470100be2000000000000077470100bf2000000000000081470100c0200000000000008b470100c12000000000000095470100c2200000000000009f470100c320000000000000a9470100c420000000000000b3470100c520000000000000bd470100c620000000000000c7470100c720000000000000d1470100c820000000000000db470100c920000000000000e5470100ca20000000000000ef470100cb20000000000000f9470100cc2000000000000003480100cd200000000000000d480100ce2000000000000017480100cf2000000000000021480100d0200000000000002b480100d12000000000000035480100d2200000000000003f480100d32000000000000049480100d42000000000000053480100d5200000000000005d480100d62000000000000067480100d72000000000000071480100d8200000000000007b480100d92000000000000085480100da200000000000008f480100db2000000000000099480100dc20000000000000a3480100dd20000000000000ad480100de20000000000000b7480100df20000000000000c1480100e020000000000000cb480100e120000000000000d5480100e220000000000000df480100e320000000000000e9480100e420000000000000f3480100e520000000000000fd480100e62000000000000007490100e72000000000000011490100e8200000000000001b490100e92000000000000025490100ea200000000000002f490100eb2000000000000039490100ec2000000000000043490100ed200000000000004d490100ee2000000000000057490100ef2000000000000061490100f0200000000000006b490100f12000000000000075490100f2200000000000007f490100f32000000000000089490100f42000000000000093490100f5200000000000009d490100f620000000000000a7490100f720000000000000b1490100f820000000000000bb490100f920000000000000c5490100fa20000000000000cf490100fb20000000000000d9490100fc20000000000000e3490100fd20000000000000ed490100fe20000000000000f7490100ff20000000000000014a010000210000000000000b4a01000121000000000000154a010002210000000000001f4a01000321000000000000294a01000421000000000000334a010005210000000000003d4a01000621000000000000474a01000721000000000000514a010008210000000000005b4a01000921000000000000654a01000a210000000000006f4a01000b21000000000000794a01000c21000000000000834a01000d210000000000008d4a01000e21000000000000974a01000f21000000000000a14a01001021000000000000ab4a01001121000000000000b54a01001221000000000000bf4a01001321000000000000c94a01001421000000000000d34a01001521000000000000dd4a01001621000000000000e74a01001721000000000000f14a01001821000000000000fb4a01001921000000000000054b01001a210000000000000f4b01001b21000000000000194b01001c21000000000000234b01001d210000000000002d4b01001e21000000000000374b01001f21000000000000414b010020210000000000004b4b01002121000000000000554b010022210000000000005f4b01002321000000000000694b01002421000000000000734b010025210000000000007d4b01002621000000000000874b01002721000000000000914b010028210000000000009b4b01002921000000000000a54b01002a21000000000000af4b01002b21000000000000b94b01002c21000000000000c34b01002d21000000000000cd4b01002e21000000000000d74b01002f21000000000000e14b01003021000000000000eb4b01003121000000000000f54b01003221000000000000ff4b01003321000000000000094c01003421000000000000134c010035210000000000001d4c01003621000000000000274c01003721000000000000314c010038210000000000003b4c01003921000000000000454c01003a210000000000004f4c01003b21000000000000594c01003c21000000000000634c01003d210000000000006d4c01003e21000000000000774c01003f21000000000000814c010040210000000000008b4c01004121000000000000954c010042210000000000009f4c01004321000000000000a94c01004421000000000000b34c01004521000000000000bd4c01004621000000000000c74c01004721000000000000d14c01004821000000000000db4c01004921000000000000e54c01004a21000000000000ef4c01004b21000000000000f94c01004c21000000000000034d01004d210000000000000d4d01004e21000000000000174d01004f21000000000000214d010050210000000000002b4d01005121000000000000354d010052210000000000003f4d01005321000000000000494d01005421000000000000534d010055210000000000005d4d01005621000000000000674d01005721000000000000714d010058210000000000007b4d01005921000000000000854d01005a210000000000008f4d01005b21000000000000994d01005c21000000000000a34d01005d21000000000000ad4d01005e21000000000000b74d01005f21000000000000c14d01006021000000000000cb4d01006121000000000000d54d01006221000000000000df4d01006321000000000000e94d01006421000000000000f34d01006521000000000000fd4d01006621000000000000074e01006721000000000000114e010068210000000000001b4e01006921000000000000254e01006a210000000000002f4e01006b21000000000000394e01006c21000000000000434e01006d210000000000004d4e01006e21000000000000574e01006f21000000000000614e010070210000000000006b4e01007121000000000000754e010072210000000000007f4e01007321000000000000894e01007421000000000000934e010075210000000000009d4e01007621000000000000a74e01007721000000000000b14e01007821000000000000bb4e01007921000000000000c54e01007a21000000000000cf4e01007b21000000000000d94e01007c21000000000000e34e01007d21000000000000ed4e01007e21000000000000f74e01007f21000000000000014f010080210000000000000b4f01008121000000000000154f010082210000000000001f4f01008321000000000000294f01008421000000000000334f010085210000000000003d4f01008621000000000000474f01008721000000000000514f010088210000000000005b4f01008921000000000000654f01008a210000000000006f4f01008b21000000000000794f01008c21000000000000834f01008d210000000000008d4f01008e21000000000000974f01008f21000000000000a14f01009021000000000000ab4f01009121000000000000b54f01009221000000000000bf4f01009321000000000000c94f01009421000000000000d34f01009521000000000000dd4f01009621000000000000e74f01009721000000000000f14f01009821000000000000fb4f01009921000000000000055001009a210000000000000f5001009b21000000000000195001009c21000000000000235001009d210000000000002d5001009e21000000000000375001009f2100000000000041500100a0210000000000004b500100a12100000000000055500100a2210000000000005f500100a32100000000000069500100a42100000000000073500100a5210000000000007d500100a62100000000000087500100a72100000000000091500100a8210000000000009b500100a921000000000000a5500100aa21000000000000af500100ab21000000000000b9500100ac21000000000000c3500100ad21000000000000cd500100ae21000000000000d7500100af21000000000000e1500100b021000000000000eb500100b121000000000000f5500100b221000000000000ff500100b32100000000000009510100b42100000000000013510100b5210000000000001d510100b62100000000000027510100b72100000000000031510100b8210000000000003b510100b92100000000000045510100ba210000000000004f510100bb2100000000000059510100bc2100000000000063510100bd210000000000006d510100be2100000000000077510100bf2100000000000081510100c0210000000000008b510100c12100000000000095510100c2210000000000009f510100c321000000000000a9510100c421000000000000b3510100c521000000000000bd510100c621000000000000c7510100c721000000000000d1510100c821000000000000db510100c921000000000000e5510100ca21000000000000ef510100cb21000000000000f9510100cc2100000000000003520100cd210000000000000d520100ce2100000000000017520100cf2100000000000021520100d0210000000000002b520100d12100000000000035520100d2210000000000003f520100d32100000000000049520100d42100000000000053520100d5210000000000005d520100d62100000000000067520100d72100000000000071520100d8210000000000007b520100d92100000000000085520100da210000000000008f520100db2100000000000099520100dc21000000000000a3520100dd21000000000000ad520100de21000000000000b7520100df21000000000000c1520100e021000000000000cb520100e121000000000000d5520100e221000000000000df520100e321000000000000e9520100e421000000000000f3520100e521000000000000fd520100e62100000000000007530100e72100000000000011530100e8210000000000001b530100e92100000000000025530100ea210000000000002f530100eb2100000000000039530100ec2100000000000043530100ed210000000000004d530100ee2100000000000057530100ef2100000000000061530100f0210000000000006b530100f12100000000000075530100f2210000000000007f530100f32100000000000089530100f42100000000000093530100f5210000000000009d530100f621000000000000a7530100f721000000000000b1530100f821000000000000bb530100f921000000000000c5530100fa21000000000000cf530100fb21000000000000d9530100fc21000000000000e3530100fd21000000000000ed530100fe21000000000000f7530100ff210000000000000154010000220000000000000b54010001220000000000001554010002220000000000001f54010003220000000000002954010004220000000000003354010005220000000000003d54010006220000000000004754010007220000000000005154010008220000000000005b5401000922000000000000655401000a220000000000006f5401000b22000000000000795401000c22000000000000835401000d220000000000008d5401000e22000000000000975401000f22000000000000a15401001022000000000000ab5401001122000000000000b55401001222000000000000bf5401001322000000000000c95401001422000000000000d35401001522000000000000dd5401001622000000000000e75401001722000000000000f15401001822000000000000fb5401001922000000000000055501001a220000000000000f5501001b22000000000000195501001c22000000000000235501001d220000000000002d5501001e22000000000000375501001f220000000000004155010020220000000000004b55010021220000000000005555010022220000000000005f55010023220000000000006955010024220000000000007355010025220000000000007d55010026220000000000008755010027220000000000009155010028220000000000009b5501002922000000000000a55501002a22000000000000af5501002b22000000000000b95501002c22000000000000c35501002d22000000000000cd5501002e22000000000000d75501002f22000000000000e15501003022000000000000eb5501003122000000000000f55501003222000000000000ff55010033220000000000000956010034220000000000001356010035220000000000001d56010036220000000000002756010037220000000000003156010038220000000000003b5601003922000000000000455601003a220000000000004f5601003b22000000000000595601003c22000000000000635601003d220000000000006d5601003e22000000000000775601003f220000000000008156010040220000000000008b56010041220000000000009556010042220000000000009f5601004322000000000000a95601004422000000000000b35601004522000000000000bd5601004622000000000000c75601004722000000000000d15601004822000000000000db5601004922000000000000e55601004a22000000000000ef5601004b22000000000000f95601004c22000000000000035701004d220000000000000d5701004e22000000000000175701004f220000000000002157010050220000000000002b57010051220000000000003557010052220000000000003f57010053220000000000004957010054220000000000005357010055220000000000005d57010056220000000000006757010057220000000000007157010058220000000000007b5701005922000000000000855701005a220000000000008f5701005b22000000000000995701005c22000000000000a35701005d22000000000000ad5701005e22000000000000b75701005f22000000000000c15701006022000000000000cb5701006122000000000000d55701006222000000000000df5701006322000000000000e95701006422000000000000f35701006522000000000000fd57010066220000000000000758010067220000000000001158010068220000000000001b5801006922000000000000255801006a220000000000002f5801006b22000000000000395801006c22000000000000435801006d220000000000004d5801006e22000000000000575801006f220000000000006158010070220000000000006b58010071220000000000007558010072220000000000007f58010073220000000000008958010074220000000000009358010075220000000000009d5801007622000000000000a75801007722000000000000b15801007822000000000000bb5801007922000000000000c55801007a22000000000000cf5801007b22000000000000d95801007c22000000000000e35801007d22000000000000ed5801007e22000000000000f75801007f220000000000000159010080220000000000000b59010081220000000000001559010082220000000000001f59010083220000000000002959010084220000000000003359010085220000000000003d59010086220000000000004759010087220000000000005159010088220000000000005b5901008922000000000000655901008a220000000000006f5901008b22000000000000795901008c22000000000000835901008d220000000000008d5901008e22000000000000975901008f22000000000000a15901009022000000000000ab5901009122000000000000b55901009222000000000000bf5901009322000000000000c95901009422000000000000d35901009522000000000000dd5901009622000000000000e75901009722000000000000f15901009822000000000000fb5901009922000000000000055a01009a220000000000000f5a01009b22000000000000195a01009c22000000000000235a01009d220000000000002d5a01009e22000000000000375a01009f22000000000000415a0100a0220000000000004b5a0100a122000000000000555a0100a2220000000000005f5a0100a322000000000000695a0100a422000000000000735a0100a5220000000000007d5a0100a622000000000000875a0100a722000000000000915a0100a8220000000000009b5a0100a922000000000000a55a0100aa22000000000000af5a0100ab22000000000000b95a0100ac22000000000000c35a0100ad22000000000000cd5a0100ae22000000000000d75a0100af22000000000000e15a0100b022000000000000eb5a0100b122000000000000f55a0100b222000000000000ff5a0100b322000000000000095b0100b422000000000000135b0100b5220000000000001d5b0100b622000000000000275b0100b722000000000000315b0100b8220000000000003b5b0100b922000000000000455b0100ba220000000000004f5b0100bb22000000000000595b0100bc22000000000000635b0100bd220000000000006d5b0100be22000000000000775b0100bf22000000000000815b0100c0220000000000008b5b0100c122000000000000955b0100c2220000000000009f5b0100c322000000000000a95b0100c422000000000000b35b0100c522000000000000bd5b0100c622000000000000c75b0100c722000000000000d15b0100c822000000000000db5b0100c922000000000000e55b0100ca22000000000000ef5b0100cb22000000000000f95b0100cc22000000000000035c0100cd220000000000000d5c0100ce22000000000000175c0100cf22000000000000215c0100d0220000000000002b5c0100d122000000000000355c0100d2220000000000003f5c0100d322000000000000495c0100d422000000000000535c0100d5220000000000005d5c0100d622000000000000675c0100d722000000000000715c0100d8220000000000007b5c0100d922000000000000855c0100da220000000000008f5c0100db22000000000000995c0100dc22000000000000a35c0100dd22000000000000ad5c0100de22000000000000b75c0100df22000000000000c15c0100e022000000000000cb5c0100e122000000000000d55c0100e222000000000000df5c0100e322000000000000e95c0100e422000000000000f35c0100e522000000000000fd5c0100e622000000000000075d0100e722000000000000115d0100e8220000000000001b5d0100e922000000000000255d0100ea220000000000002f5d0100eb22000000000000395d0100ec22000000000000435d0100ed220000000000004d5d0100ee22000000000000575d0100ef22000000000000615d0100f0220000000000006b5d0100f122000000000000755d0100f2220000000000007f5d0100f322000000000000895d0100f422000000000000935d0100f5220000000000009d5d0100f622000000000000a75d0100f722000000000000b15d0100f822000000000000bb5d0100f922000000000000c55d0100fa22000000000000cf5d0100fb22000000000000d95d0100fc22000000000000e35d0100fd22000000000000ed5d0100fe22000000000000f75d0100ff22000000000000015e010000230000000000000b5e01000123000000000000155e010002230000000000001f5e01000323000000000000295e01000423000000000000335e010005230000000000003d5e01000623000000000000475e01000723000000000000515e010008230000000000005b5e01000923000000000000655e01000a230000000000006f5e01000b23000000000000795e01000c23000000000000835e01000d230000000000008d5e01000e23000000000000975e01000f23000000000000a15e01001023000000000000ab5e01001123000000000000b55e01001223000000000000bf5e01001323000000000000c95e01001423000000000000d35e01001523000000000000dd5e01001623000000000000e75e01001723000000000000f15e01001823000000000000fb5e01001923000000000000055f01001a230000000000000f5f01001b23000000000000195f01001c23000000000000235f01001d230000000000002d5f01001e23000000000000375f01001f23000000000000415f010020230000000000004b5f01002123000000000000555f010022230000000000005f5f01002323000000000000695f01002423000000000000735f010025230000000000007d5f01002623000000000000875f01002723000000000000915f010028230000000000009b5f01002923000000000000a55f01002a23000000000000af5f01002b23000000000000b95f01002c23000000000000c35f01002d23000000000000cd5f01002e23000000000000d75f01002f23000000000000e15f01003023000000000000eb5f01003123000000000000f55f01003223000000000000ff5f010033230000000000000960010034230000000000001360010035230000000000001d60010036230000000000002760010037230000000000003160010038230000000000003b6001003923000000000000456001003a230000000000004f6001003b23000000000000596001003c23000000000000636001003d230000000000006d6001003e23000000000000776001003f230000000000008160010040230000000000008b60010041230000000000009560010042230000000000009f6001004323000000000000a96001004423000000000000b36001004523000000000000bd6001004623000000000000c76001004723000000000000d16001004823000000000000db6001004923000000000000e56001004a23000000000000ef6001004b23000000000000f96001004c23000000000000036101004d230000000000000d6101004e23000000000000176101004f230000000000002161010050230000000000002b61010051230000000000003561010052230000000000003f61010053230000000000004961010054230000000000005361010055230000000000005d61010056230000000000006761010057230000000000007161010058230000000000007b6101005923000000000000856101005a230000000000008f6101005b23000000000000996101005c23000000000000a36101005d23000000000000ad6101005e23000000000000b76101005f23000000000000c16101006023000000000000cb6101006123000000000000d56101006223000000000000df6101006323000000000000e96101006423000000000000f36101006523000000000000fd61010066230000000000000762010067230000000000001162010068230000000000001b6201006923000000000000256201006a230000000000002f6201006b23000000000000396201006c23000000000000436201006d230000000000004d6201006e23000000000000576201006f230000000000006162010070230000000000006b62010071230000000000007562010072230000000000007f62010073230000000000008962010074230000000000009362010075230000000000009d6201007623000000000000a76201007723000000000000b16201007823000000000000bb6201007923000000000000c56201007a23000000000000cf6201007b23000000000000d96201007c23000000000000e36201007d23000000000000ed6201007e23000000000000f76201007f230000000000000163010080230000000000000b63010081230000000000001563010082230000000000001f63010083230000000000002963010084230000000000003363010085230000000000003d63010086230000000000004763010087230000000000005163010088230000000000005b6301008923000000000000656301008a230000000000006f6301008b23000000000000796301008c23000000000000836301008d230000000000008d6301008e23000000000000976301008f23000000000000a16301009023000000000000ab6301009123000000000000b56301009223000000000000bf6301009323000000000000c96301009423000000000000d36301009523000000000000dd6301009623000000000000e76301009723000000000000f16301009823000000000000fb6301009923000000000000056401009a230000000000000f6401009b23000000000000196401009c23000000000000236401009d230000000000002d6401009e23000000000000376401009f2300000000000041640100a0230000000000004b640100a12300000000000055640100a2230000000000005f640100a32300000000000069640100a42300000000000073640100a5230000000000007d640100a62300000000000087640100a72300000000000091640100a8230000000000009b640100a923000000000000a5640100aa23000000000000af640100ab23000000000000b9640100ac23000000000000c3640100ad23000000000000cd640100ae23000000000000d7640100af23000000000000e1640100b023000000000000eb640100b123000000000000f5640100b223000000000000ff640100b32300000000000009650100b42300000000000013650100b5230000000000001d650100b62300000000000027650100b72300000000000031650100b8230000000000003b650100b92300000000000045650100ba230000000000004f650100bb2300000000000059650100bc2300000000000063650100bd230000000000006d650100be2300000000000077650100bf2300000000000081650100c0230000000000008b650100c12300000000000095650100c2230000000000009f650100c323000000000000a9650100c423000000000000b3650100c523000000000000bd650100c623000000000000c7650100c723000000000000d1650100c823000000000000db650100c923000000000000e5650100ca23000000000000ef650100cb23000000000000f9650100cc2300000000000003660100cd230000000000000d660100ce2300000000000017660100cf2300000000000021660100d0230000000000002b660100d12300000000000035660100d2230000000000003f660100d32300000000000049660100d42300000000000053660100d5230000000000005d660100d62300000000000067660100d72300000000000071660100d8230000000000007b660100d92300000000000085660100da230000000000008f660100db2300000000000099660100dc23000000000000a3660100dd23000000000000ad660100de23000000000000b7660100df23000000000000c1660100e023000000000000cb660100e123000000000000d5660100e223000000000000df660100e323000000000000e9660100e423000000000000f3660100e523000000000000fd660100e62300000000000007670100e72300000000000011670100e8230000000000001b670100e92300000000000025670100ea230000000000002f670100eb2300000000000039670100ec2300000000000043670100ed230000000000004d670100ee2300000000000057670100ef2300000000000061670100f0230000000000006b670100f12300000000000075670100f2230000000000007f670100f32300000000000089670100f42300000000000093670100f5230000000000009d670100f623000000000000a7670100f723000000000000b1670100f823000000000000bb670100f923000000000000c5670100fa23000000000000cf670100fb23000000000000d9670100fc23000000000000e3670100fd23000000000000ed670100fe23000000000000f7670100ff230000000000000168010000240000000000000b68010001240000000000001568010002240000000000001f68010003240000000000002968010004240000000000003368010005240000000000003d68010006240000000000004768010007240000000000005168010008240000000000005b6801000924000000000000656801000a240000000000006f6801000b24000000000000796801000c24000000000000836801000d240000000000008d6801000e24000000000000976801000f24000000000000a16801001024000000000000ab6801001124000000000000b56801001224000000000000bf6801001324000000000000c96801001424000000000000d36801001524000000000000dd6801001624000000000000e76801001724000000000000f16801001824000000000000fb6801001924000000000000056901001a240000000000000f6901001b24000000000000196901001c24000000000000236901001d240000000000002d6901001e24000000000000376901001f240000000000004169010020240000000000004b69010021240000000000005569010022240000000000005f69010023240000000000006969010024240000000000007369010025240000000000007d69010026240000000000008769010027240000000000009169010028240000000000009b6901002924000000000000a56901002a24000000000000af6901002b24000000000000b96901002c24000000000000c36901002d24000000000000cd6901002e24000000000000d76901002f24000000000000e16901003024000000000000eb6901003124000000000000f56901003224000000000000ff6901003324000000000000096a01003424000000000000136a010035240000000000001d6a01003624000000000000276a01003724000000000000316a010038240000000000003b6a01003924000000000000456a01003a240000000000004f6a01003b24000000000000596a01003c24000000000000636a01003d240000000000006d6a01003e24000000000000776a01003f24000000000000816a010040240000000000008b6a01004124000000000000956a010042240000000000009f6a01004324000000000000a96a01004424000000000000b36a01004524000000000000bd6a01004624000000000000c76a01004724000000000000d16a01004824000000000000db6a01004924000000000000e56a01004a24000000000000ef6a01004b24000000000000f96a01004c24000000000000036b01004d240000000000000d6b01004e24000000000000176b01004f24000000000000216b010050240000000000002b6b01005124000000000000356b010052240000000000003f6b01005324000000000000496b01005424000000000000536b010055240000000000005d6b01005624000000000000676b01005724000000000000716b010058240000000000007b6b01005924000000000000856b01005a240000000000008f6b01005b24000000000000996b01005c24000000000000a36b01005d24000000000000ad6b01005e24000000000000b76b01005f24000000000000c16b01006024000000000000cb6b01006124000000000000d56b01006224000000000000df6b01006324000000000000e96b01006424000000000000f36b01006524000000000000fd6b01006624000000000000076c01006724000000000000116c010068240000000000001b6c01006924000000000000256c01006a240000000000002f6c01006b24000000000000396c01006c24000000000000436c01006d240000000000004d6c01006e24000000000000576c01006f24000000000000616c010070240000000000006b6c01007124000000000000756c010072240000000000007f6c01007324000000000000896c01007424000000000000936c010075240000000000009d6c01007624000000000000a76c01007724000000000000b16c01007824000000000000bb6c01007924000000000000c56c01007a24000000000000cf6c01007b24000000000000d96c01007c24000000000000e36c01007d24000000000000ed6c01007e24000000000000f76c01007f24000000000000016d010080240000000000000b6d01008124000000000000156d010082240000000000001f6d01008324000000000000296d01008424000000000000336d010085240000000000003d6d01008624000000000000476d01008724000000000000516d010088240000000000005b6d01008924000000000000656d01008a240000000000006f6d01008b24000000000000796d01008c24000000000000836d01008d240000000000008d6d01008e24000000000000976d01008f24000000000000a16d01009024000000000000ab6d01009124000000000000b56d01009224000000000000bf6d01009324000000000000c96d01009424000000000000d36d01009524000000000000dd6d01009624000000000000e76d01009724000000000000f16d01009824000000000000fb6d01009924000000000000056e01009a240000000000000f6e01009b24000000000000196e01009c24000000000000236e01009d240000000000002d6e01009e24000000000000376e01009f24000000000000416e0100a0240000000000004b6e0100a124000000000000556e0100a2240000000000005f6e0100a324000000000000696e0100a424000000000000736e0100a5240000000000007d6e0100a624000000000000876e0100a724000000000000916e0100a8240000000000009b6e0100a924000000000000a56e0100aa24000000000000af6e0100ab24000000000000b96e0100ac24000000000000c36e0100ad24000000000000cd6e0100ae24000000000000d76e0100af24000000000000e16e0100b024000000000000eb6e0100b124000000000000f56e0100b224000000000000ff6e0100b324000000000000096f0100b424000000000000136f0100b5240000000000001d6f0100b624000000000000276f0100b724000000000000316f0100b8240000000000003b6f0100b924000000000000456f0100ba240000000000004f6f0100bb24000000000000596f0100bc24000000000000636f0100bd240000000000006d6f0100be24000000000000776f0100bf24000000000000816f0100c0240000000000008b6f0100c124000000000000956f0100c2240000000000009f6f0100c324000000000000a96f0100c424000000000000b36f0100c524000000000000bd6f0100c624000000000000c76f0100c724000000000000d16f0100c824000000000000db6f0100c924000000000000e56f0100ca24000000000000ef6f0100cb24000000000000f96f0100cc2400000000000003700100cd240000000000000d700100ce2400000000000017700100cf2400000000000021700100d0240000000000002b700100d12400000000000035700100d2240000000000003f700100d32400000000000049700100d42400000000000053700100d5240000000000005d700100d62400000000000067700100d72400000000000071700100d8240000000000007b700100d92400000000000085700100da240000000000008f700100db2400000000000099700100dc24000000000000a3700100dd24000000000000ad700100de24000000000000b7700100df24000000000000c1700100e024000000000000cb700100e124000000000000d5700100e224000000000000df700100e324000000000000e9700100e424000000000000f3700100e524000000000000fd700100e62400000000000007710100e72400000000000011710100e8240000000000001b710100e92400000000000025710100ea240000000000002f710100eb2400000000000039710100ec2400000000000043710100ed240000000000004d710100ee2400000000000057710100ef2400000000000061710100f0240000000000006b710100f12400000000000075710100f2240000000000007f710100f32400000000000089710100f42400000000000093710100f5240000000000009d710100f624000000000000a7710100f724000000000000b1710100f824000000000000bb710100f924000000000000c5710100fa24000000000000cf710100fb24000000000000d9710100fc24000000000000e3710100fd24000000000000ed710100fe24000000000000f7710100ff240000000000000172010000250000000000000b72010001250000000000001572010002250000000000001f72010003250000000000002972010004250000000000003372010005250000000000003d72010006250000000000004772010007250000000000005172010008250000000000005b7201000925000000000000657201000a250000000000006f7201000b25000000000000797201000c25000000000000837201000d250000000000008d7201000e25000000000000977201000f25000000000000a17201001025000000000000ab7201001125000000000000b57201001225000000000000bf7201001325000000000000c97201001425000000000000d37201001525000000000000dd7201001625000000000000e77201001725000000000000f17201001825000000000000fb7201001925000000000000057301001a250000000000000f7301001b25000000000000197301001c25000000000000237301001d250000000000002d7301001e25000000000000377301001f250000000000004173010020250000000000004b73010021250000000000005573010022250000000000005f73010023250000000000006973010024250000000000007373010025250000000000007d73010026250000000000008773010027250000000000009173010028250000000000009b7301002925000000000000a57301002a25000000000000af7301002b25000000000000b97301002c25000000000000c37301002d25000000000000cd7301002e25000000000000d77301002f25000000000000e17301003025000000000000eb7301003125000000000000f57301003225000000000000ff73010033250000000000000974010034250000000000001374010035250000000000001d74010036250000000000002774010037250000000000003174010038250000000000003b7401003925000000000000457401003a250000000000004f7401003b25000000000000597401003c25000000000000637401003d250000000000006d7401003e25000000000000777401003f250000000000008174010040250000000000008b74010041250000000000009574010042250000000000009f7401004325000000000000a97401004425000000000000b37401004525000000000000bd7401004625000000000000c77401004725000000000000d17401004825000000000000db7401004925000000000000e57401004a25000000000000ef7401004b25000000000000f97401004c25000000000000037501004d250000000000000d7501004e25000000000000177501004f250000000000002175010050250000000000002b75010051250000000000003575010052250000000000003f75010053250000000000004975010054250000000000005375010055250000000000005d75010056250000000000006775010057250000000000007175010058250000000000007b7501005925000000000000857501005a250000000000008f7501005b25000000000000997501005c25000000000000a37501005d25000000000000ad7501005e25000000000000b77501005f25000000000000c17501006025000000000000cb7501006125000000000000d57501006225000000000000df7501006325000000000000e97501006425000000000000f37501006525000000000000fd75010066250000000000000776010067250000000000001176010068250000000000001b7601006925000000000000257601006a250000000000002f7601006b25000000000000397601006c25000000000000437601006d250000000000004d7601006e25000000000000577601006f250000000000006176010070250000000000006b76010071250000000000007576010072250000000000007f76010073250000000000008976010074250000000000009376010075250000000000009d7601007625000000000000a77601007725000000000000b17601007825000000000000bb7601007925000000000000c57601007a25000000000000cf7601007b25000000000000d97601007c25000000000000e37601007d25000000000000ed7601007e25000000000000f77601007f250000000000000177010080250000000000000b77010081250000000000001577010082250000000000001f77010083250000000000002977010084250000000000003377010085250000000000003d77010086250000000000004777010087250000000000005177010088250000000000005b7701008925000000000000657701008a250000000000006f7701008b25000000000000797701008c25000000000000837701008d250000000000008d7701008e25000000000000977701008f25000000000000a17701009025000000000000ab7701009125000000000000b57701009225000000000000bf7701009325000000000000c97701009425000000000000d37701009525000000000000dd7701009625000000000000e77701009725000000000000f17701009825000000000000fb7701009925000000000000057801009a250000000000000f7801009b25000000000000197801009c25000000000000237801009d250000000000002d7801009e25000000000000377801009f2500000000000041780100a0250000000000004b780100a12500000000000055780100a2250000000000005f780100a32500000000000069780100a42500000000000073780100a5250000000000007d780100a62500000000000087780100a72500000000000091780100a8250000000000009b780100a925000000000000a5780100aa25000000000000af780100ab25000000000000b9780100ac25000000000000c3780100ad25000000000000cd780100ae25000000000000d7780100af25000000000000e1780100b025000000000000eb780100b125000000000000f5780100b225000000000000ff780100b32500000000000009790100b42500000000000013790100b5250000000000001d790100b62500000000000027790100b72500000000000031790100b8250000000000003b790100b92500000000000045790100ba250000000000004f790100bb2500000000000059790100bc2500000000000063790100bd250000000000006d790100be2500000000000077790100bf2500000000000081790100c0250000000000008b790100c12500000000000095790100c2250000000000009f790100c325000000000000a9790100c425000000000000b3790100c525000000000000bd790100c625000000000000c7790100c725000000000000d1790100c825000000000000db790100c925000000000000e5790100ca25000000000000ef790100cb25000000000000f9790100cc25000000000000037a0100cd250000000000000d7a0100ce25000000000000177a0100cf25000000000000217a0100d0250000000000002b7a0100d125000000000000357a0100d2250000000000003f7a0100d325000000000000497a0100d425000000000000537a0100d5250000000000005d7a0100d625000000000000677a0100d725000000000000717a0100d8250000000000007b7a0100d925000000000000857a0100da250000000000008f7a0100db25000000000000997a0100dc25000000000000a37a0100dd25000000000000ad7a0100de25000000000000b77a0100df25000000000000c17a0100e025000000000000cb7a0100e125000000000000d57a0100e225000000000000df7a0100e325000000000000e97a0100e425000000000000f37a0100e525000000000000fd7a0100e625000000000000077b0100e725000000000000117b0100e8250000000000001b7b0100e925000000000000257b0100ea250000000000002f7b0100eb25000000000000397b0100ec25000000000000437b0100ed250000000000004d7b0100ee25000000000000577b0100ef25000000000000617b0100f0250000000000006b7b0100f125000000000000757b0100f2250000000000007f7b0100f325000000000000897b0100f425000000000000937b0100f5250000000000009d7b0100f625000000000000a77b0100f725000000000000b17b0100f825000000000000bb7b0100f925000000000000c57b0100fa25000000000000cf7b0100fb25000000000000d97b0100fc25000000000000e37b0100fd25000000000000ed7b0100fe25000000000000f77b0100ff25000000000000017c010000260000000000000b7c01000126000000000000157c010002260000000000001f7c01000326000000000000297c01000426000000000000337c010005260000000000003d7c01000626000000000000477c01000726000000000000517c010008260000000000005b7c01000926000000000000657c01000a260000000000006f7c01000b26000000000000797c01000c26000000000000837c01000d260000000000008d7c01000e26000000000000977c01000f26000000000000a17c01001026000000000000ab7c01001126000000000000b57c01001226000000000000bf7c01001326000000000000c97c01001426000000000000d37c01001526000000000000dd7c01001626000000000000e77c01001726000000000000f17c01001826000000000000fb7c01001926000000000000057d01001a260000000000000f7d01001b26000000000000197d01001c26000000000000237d01001d260000000000002d7d01001e26000000000000377d01001f26000000000000417d010020260000000000004b7d01002126000000000000557d010022260000000000005f7d01002326000000000000697d01002426000000000000737d010025260000000000007d7d01002626000000000000877d01002726000000000000917d010028260000000000009b7d01002926000000000000a57d01002a26000000000000af7d01002b26000000000000b97d01002c26000000000000c37d01002d26000000000000cd7d01002e26000000000000d77d01002f26000000000000e17d01003026000000000000eb7d01003126000000000000f57d01003226000000000000ff7d01003326000000000000097e01003426000000000000137e010035260000000000001d7e01003626000000000000277e01003726000000000000317e010038260000000000003b7e01003926000000000000457e01003a260000000000004f7e01003b26000000000000597e01003c26000000000000637e01003d260000000000006d7e01003e26000000000000777e01003f26000000000000817e010040260000000000008b7e01004126000000000000957e010042260000000000009f7e01004326000000000000a97e01004426000000000000b37e01004526000000000000bd7e01004626000000000000c77e01004726000000000000d17e01004826000000000000db7e01004926000000000000e57e01004a26000000000000ef7e01004b26000000000000f97e01004c26000000000000037f01004d260000000000000d7f01004e26000000000000177f01004f26000000000000217f010050260000000000002b7f01005126000000000000357f010052260000000000003f7f01005326000000000000497f01005426000000000000537f010055260000000000005d7f01005626000000000000677f01005726000000000000717f010058260000000000007b7f01005926000000000000857f01005a260000000000008f7f01005b26000000000000997f01005c26000000000000a37f01005d26000000000000ad7f01005e26000000000000b77f01005f26000000000000c17f01006026000000000000cb7f01006126000000000000d57f01006226000000000000df7f01006326000000000000e97f01006426000000000000f37f01006526000000000000fd7f010066260000000000000780010067260000000000001180010068260000000000001b8001006926000000000000258001006a260000000000002f8001006b26000000000000398001006c26000000000000438001006d260000000000004d8001006e26000000000000578001006f260000000000006180010070260000000000006b80010071260000000000007580010072260000000000007f80010073260000000000008980010074260000000000009380010075260000000000009d8001007626000000000000a78001007726000000000000b18001007826000000000000bb8001007926000000000000c58001007a26000000000000cf8001007b26000000000000d98001007c26000000000000e38001007d26000000000000ed8001007e26000000000000f78001007f260000000000000181010080260000000000000b81010081260000000000001581010082260000000000001f81010083260000000000002981010084260000000000003381010085260000000000003d81010086260000000000004781010087260000000000005181010088260000000000005b8101008926000000000000658101008a260000000000006f8101008b26000000000000798101008c26000000000000838101008d260000000000008d8101008e26000000000000978101008f26000000000000a18101009026000000000000ab8101009126000000000000b58101009226000000000000bf8101009326000000000000c98101009426000000000000d38101009526000000000000dd8101009626000000000000e78101009726000000000000f18101009826000000000000fb8101009926000000000000058201009a260000000000000f8201009b26000000000000198201009c26000000000000238201009d260000000000002d8201009e26000000000000378201009f2600000000000041820100a0260000000000004b820100a12600000000000055820100a2260000000000005f820100a32600000000000069820100a42600000000000073820100a5260000000000007d820100a62600000000000087820100a72600000000000091820100a8260000000000009b820100a926000000000000a5820100aa26000000000000af820100ab26000000000000b9820100ac26000000000000c3820100ad26000000000000cd820100ae26000000000000d7820100af26000000000000e1820100b026000000000000eb820100b126000000000000f5820100b226000000000000ff820100b32600000000000009830100b42600000000000013830100b5260000000000001d830100b62600000000000027830100b72600000000000031830100b8260000000000003b830100b92600000000000045830100ba260000000000004f830100bb2600000000000059830100bc2600000000000063830100bd260000000000006d830100be2600000000000077830100bf2600000000000081830100c0260000000000008b830100c12600000000000095830100c2260000000000009f830100c326000000000000a9830100c426000000000000b3830100c526000000000000bd830100c626000000000000c7830100c726000000000000d1830100c826000000000000db830100c926000000000000e5830100ca26000000000000ef830100cb26000000000000f9830100cc2600000000000003840100cd260000000000000d840100ce2600000000000017840100cf2600000000000021840100d0260000000000002b840100d12600000000000035840100d2260000000000003f840100d32600000000000049840100d42600000000000053840100d5260000000000005d840100d62600000000000067840100d72600000000000071840100d8260000000000007b840100d92600000000000085840100da260000000000008f840100db2600000000000099840100dc26000000000000a3840100dd26000000000000ad840100de26000000000000b7840100df26000000000000c1840100e026000000000000cb840100e126000000000000d5840100e226000000000000df840100e326000000000000e9840100e426000000000000f3840100e526000000000000fd840100e62600000000000007850100e72600000000000011850100e8260000000000001b850100e92600000000000025850100ea260000000000002f850100eb2600000000000039850100ec2600000000000043850100ed260000000000004d850100ee2600000000000057850100ef2600000000000061850100f0260000000000006b850100f12600000000000075850100f2260000000000007f850100f32600000000000089850100f42600000000000093850100f5260000000000009d850100f626000000000000a7850100f726000000000000b1850100f826000000000000bb850100f926000000000000c5850100fa26000000000000cf850100fb26000000000000d9850100fc26000000000000e3850100fd26000000000000ed850100fe26000000000000f7850100ff260000000000000186010000270000000000000b86010001270000000000001586010002270000000000001f86010003270000000000002986010004270000000000003386010005270000000000003d86010006270000000000004786010007270000000000005186010008270000000000005b8601000927000000000000658601000a270000000000006f8601000b27000000000000798601000c27000000000000838601000d270000000000008d8601000e27000000000000978601000f27000000000000a18601001027000000000000ab8601001127000000000000b58601001227000000000000bf8601001327000000000000c98601001427000000000000d38601001527000000000000dd8601001627000000000000e78601001727000000000000f18601001827000000000000fb8601001927000000000000058701001a270000000000000f8701001b27000000000000198701001c27000000000000238701001d270000000000002d8701001e27000000000000378701001f270000000000004187010020270000000000004b87010021270000000000005587010022270000000000005f87010023270000000000006987010024270000000000007387010025270000000000007d87010026270000000000008787010027270000000000009187010028270000000000009b8701002927000000000000a58701002a27000000000000af8701002b27000000000000b98701002c27000000000000c38701002d27000000000000cd8701002e27000000000000d78701002f27000000000000e18701003027000000000000eb8701003127000000000000f58701003227000000000000ff87010033270000000000000988010034270000000000001388010035270000000000001d88010036270000000000002788010037270000000000003188010038270000000000003b8801003927000000000000458801003a270000000000004f8801003b27000000000000598801003c27000000000000638801003d270000000000006d8801003e27000000000000778801003f270000000000008188010040270000000000008b88010041270000000000009588010042270000000000009f8801004327000000000000a98801004427000000000000b38801004527000000000000bd8801004627000000000000c78801004727000000000000d18801004827000000000000db8801004927000000000000e58801004a27000000000000ef8801004b27000000000000f98801004c27000000000000038901004d270000000000000d8901004e27000000000000178901004f270000000000002189010050270000000000002b89010051270000000000003589010052270000000000003f89010053270000000000004989010054270000000000005389010055270000000000005d89010056270000000000006789010057270000000000007189010058270000000000007b8901005927000000000000858901005a270000000000008f8901005b27000000000000998901005c27000000000000a38901005d27000000000000ad8901005e27000000000000b78901005f27000000000000c18901006027000000000000cb8901006127000000000000d58901006227000000000000df8901006327000000000000e98901006427000000000000f38901006527000000000000fd8901006627000000000000078a01006727000000000000118a010068270000000000001b8a01006927000000000000258a01006a270000000000002f8a01006b27000000000000398a01006c27000000000000438a01006d270000000000004d8a01006e27000000000000578a01006f27000000000000618a010070270000000000006b8a01007127000000000000758a010072270000000000007f8a01007327000000000000898a01007427000000000000938a010075270000000000009d8a01007627000000000000a78a01007727000000000000b18a01007827000000000000bb8a01007927000000000000c58a01007a27000000000000cf8a01007b27000000000000d98a01007c27000000000000e38a01007d27000000000000ed8a01007e27000000000000f78a01007f27000000000000018b010080270000000000000b8b01008127000000000000158b010082270000000000001f8b01008327000000000000298b01008427000000000000338b010085270000000000003d8b01008627000000000000478b01008727000000000000518b010088270000000000005b8b01008927000000000000658b01008a270000000000006f8b01008b27000000000000798b01008c27000000000000838b01008d270000000000008d8b01008e27000000000000978b01008f27000000000000a18b01009027000000000000ab8b01009127000000000000b58b01009227000000000000bf8b01009327000000000000c98b01009427000000000000d38b01009527000000000000dd8b01009627000000000000e78b01009727000000000000f18b01009827000000000000fb8b01009927000000000000058c01009a270000000000000f8c01009b27000000000000198c01009c27000000000000238c01009d270000000000002d8c01009e27000000000000378c01009f27000000000000418c0100a0270000000000004b8c0100a127000000000000558c0100a2270000000000005f8c0100a327000000000000698c0100a427000000000000738c0100a5270000000000007d8c0100a627000000000000878c0100a727000000000000918c0100a8270000000000009b8c0100a927000000000000a58c0100aa27000000000000af8c0100ab27000000000000b98c0100ac27000000000000c38c0100ad27000000000000cd8c0100ae27000000000000d78c0100af27000000000000e18c0100b027000000000000eb8c0100b127000000000000f58c0100b227000000000000ff8c0100b327000000000000098d0100b427000000000000138d0100b5270000000000001d8d0100b627000000000000278d0100b727000000000000318d0100b8270000000000003b8d0100b927000000000000458d0100ba270000000000004f8d0100bb27000000000000598d0100bc27000000000000638d0100bd270000000000006d8d0100be27000000000000778d0100bf27000000000000818d0100c0270000000000008b8d0100c127000000000000958d0100c2270000000000009f8d0100c327000000000000a98d0100c427000000000000b38d0100c527000000000000bd8d0100c627000000000000c78d0100c727000000000000d18d0100c827000000000000db8d0100c927000000000000e58d0100ca27000000000000ef8d0100cb27000000000000f98d0100cc27000000000000038e0100cd270000000000000d8e0100ce27000000000000178e0100cf27000000000000218e0100d0270000000000002b8e0100d127000000000000358e0100d2270000000000003f8e0100d327000000000000498e0100d427000000000000538e0100d5270000000000005d8e0100d627000000000000678e0100d727000000000000718e0100d8270000000000007b8e0100d927000000000000858e0100da270000000000008f8e0100db27000000000000998e0100dc27000000000000a38e0100dd27000000000000ad8e0100de27000000000000b78e0100df27000000000000c18e0100e027000000000000cb8e0100e127000000000000d58e0100e227000000000000df8e0100e327000000000000e98e0100e427000000000000f38e0100e527000000000000fd8e0100e627000000000000078f0100e727000000000000118f0100e8270000000000001b8f0100e927000000000000258f0100ea270000000000002f8f0100eb27000000000000398f0100ec27000000000000438f0100ed270000000000004d8f0100ee27000000000000578f0100ef27000000000000618f0100f0270000000000006b8f0100f127000000000000758f0100f2270000000000007f8f0100f327000000000000898f0100f427000000000000938f0100f5270000000000009d8f0100f627000000000000a78f0100f727000000000000b18f0100f827000000000000bb8f0100f927000000000000c58f0100fa27000000000000cf8f0100fb27000000000000d98f0100fc27000000000000e38f0100fd27000000000000ed8f0100fe27000000000000f78f0100ff270000000000000190010000280000000000000b90010001280000000000001590010002280000000000001f90010003280000000000002990010004280000000000003390010005280000000000003d90010006280000000000004790010007280000000000005190010008280000000000005b9001000928000000000000659001000a280000000000006f9001000b28000000000000799001000c28000000000000839001000d280000000000008d9001000e28000000000000979001000f28000000000000a19001001028000000000000ab9001001128000000000000b59001001228000000000000bf9001001328000000000000c99001001428000000000000d39001001528000000000000dd9001001628000000000000e79001001728000000000000f19001001828000000000000fb9001001928000000000000059101001a280000000000000f9101001b28000000000000199101001c28000000000000239101001d280000000000002d9101001e28000000000000379101001f280000000000004191010020280000000000004b91010021280000000000005591010022280000000000005f91010023280000000000006991010024280000000000007391010025280000000000007d91010026280000000000008791010027280000000000009191010028280000000000009b9101002928000000000000a59101002a28000000000000af9101002b28000000000000b99101002c28000000000000c39101002d28000000000000cd9101002e28000000000000d79101002f28000000000000e19101003028000000000000eb9101003128000000000000f59101003228000000000000ff91010033280000000000000992010034280000000000001392010035280000000000001d92010036280000000000002792010037280000000000003192010038280000000000003b9201003928000000000000459201003a280000000000004f9201003b28000000000000599201003c28000000000000639201003d280000000000006d9201003e28000000000000779201003f280000000000008192010040280000000000008b92010041280000000000009592010042280000000000009f9201004328000000000000a99201004428000000000000b39201004528000000000000bd9201004628000000000000c79201004728000000000000d19201004828000000000000db9201004928000000000000e59201004a28000000000000ef9201004b28000000000000f99201004c28000000000000039301004d280000000000000d9301004e28000000000000179301004f280000000000002193010050280000000000002b93010051280000000000003593010052280000000000003f93010053280000000000004993010054280000000000005393010055280000000000005d93010056280000000000006793010057280000000000007193010058280000000000007b9301005928000000000000859301005a280000000000008f9301005b28000000000000999301005c28000000000000a39301005d28000000000000ad9301005e28000000000000b79301005f28000000000000c19301006028000000000000cb9301006128000000000000d59301006228000000000000df9301006328000000000000e99301006428000000000000f39301006528000000000000fd93010066280000000000000794010067280000000000001194010068280000000000001b9401006928000000000000259401006a280000000000002f9401006b28000000000000399401006c28000000000000439401006d280000000000004d9401006e28000000000000579401006f280000000000006194010070280000000000006b94010071280000000000007594010072280000000000007f94010073280000000000008994010074280000000000009394010075280000000000009d9401007628000000000000a79401007728000000000000b19401007828000000000000bb9401007928000000000000c59401007a28000000000000cf9401007b28000000000000d99401007c28000000000000e39401007d28000000000000ed9401007e28000000000000f79401007f280000000000000195010080280000000000000b95010081280000000000001595010082280000000000001f95010083280000000000002995010084280000000000003395010085280000000000003d95010086280000000000004795010087280000000000005195010088280000000000005b9501008928000000000000659501008a280000000000006f9501008b28000000000000799501008c28000000000000839501008d280000000000008d9501008e28000000000000979501008f28000000000000a19501009028000000000000ab9501009128000000000000b59501009228000000000000bf9501009328000000000000c99501009428000000000000d39501009528000000000000dd9501009628000000000000e79501009728000000000000f19501009828000000000000fb9501009928000000000000059601009a280000000000000f9601009b28000000000000199601009c28000000000000239601009d280000000000002d9601009e28000000000000379601009f2800000000000041960100a0280000000000004b960100a12800000000000055960100a2280000000000005f960100a32800000000000069960100a42800000000000073960100a5280000000000007d960100a62800000000000087960100a72800000000000091960100a8280000000000009b960100a928000000000000a5960100aa28000000000000af960100ab28000000000000b9960100ac28000000000000c3960100ad28000000000000cd960100ae28000000000000d7960100af28000000000000e1960100b028000000000000eb960100b128000000000000f5960100b228000000000000ff960100b32800000000000009970100b42800000000000013970100b5280000000000001d970100b62800000000000027970100b72800000000000031970100b8280000000000003b970100b92800000000000045970100ba280000000000004f970100bb2800000000000059970100bc2800000000000063970100bd280000000000006d970100be2800000000000077970100bf2800000000000081970100c0280000000000008b970100c12800000000000095970100c2280000000000009f970100c328000000000000a9970100c428000000000000b3970100c528000000000000bd970100c628000000000000c7970100c728000000000000d1970100c828000000000000db970100c928000000000000e5970100ca28000000000000ef970100cb28000000000000f9970100cc2800000000000003980100cd280000000000000d980100ce2800000000000017980100cf2800000000000021980100d0280000000000002b980100d12800000000000035980100d2280000000000003f980100d32800000000000049980100d42800000000000053980100d5280000000000005d980100d62800000000000067980100d72800000000000071980100d8280000000000007b980100d92800000000000085980100da280000000000008f980100db2800000000000099980100dc28000000000000a3980100dd28000000000000ad980100de28000000000000b7980100df28000000000000c1980100e028000000000000cb980100e128000000000000d5980100e228000000000000df980100e328000000000000e9980100e428000000000000f3980100e528000000000000fd980100e62800000000000007990100e72800000000000011990100e8280000000000001b990100e92800000000000025990100ea280000000000002f990100eb2800000000000039990100ec2800000000000043990100ed280000000000004d990100ee2800000000000057990100ef2800000000000061990100f0280000000000006b990100f12800000000000075990100f2280000000000007f990100f32800000000000089990100f42800000000000093990100f5280000000000009d990100f628000000000000a7990100f728000000000000b1990100f828000000000000bb990100f928000000000000c5990100fa28000000000000cf990100fb28000000000000d9990100fc28000000000000e3990100fd28000000000000ed990100fe28000000000000f7990100ff28000000000000019a010000290000000000000b9a01000129000000000000159a010002290000000000001f9a01000329000000000000299a01000429000000000000339a010005290000000000003d9a01000629000000000000479a01000729000000000000519a010008290000000000005b9a01000929000000000000659a01000a290000000000006f9a01000b29000000000000799a01000c29000000000000839a01000d290000000000008d9a01000e29000000000000979a01000f29000000000000a19a01001029000000000000ab9a01001129000000000000b59a01001229000000000000bf9a01001329000000000000c99a01001429000000000000d39a01001529000000000000dd9a01001629000000000000e79a01001729000000000000f19a01001829000000000000fb9a01001929000000000000059b01001a290000000000000f9b01001b29000000000000199b01001c29000000000000239b01001d290000000000002d9b01001e29000000000000379b01001f29000000000000419b010020290000000000004b9b01002129000000000000559b010022290000000000005f9b01002329000000000000699b01002429000000000000739b010025290000000000007d9b01002629000000000000879b01002729000000000000919b010028290000000000009b9b01002929000000000000a59b01002a29000000000000af9b01002b29000000000000b99b01002c29000000000000c39b01002d29000000000000cd9b01002e29000000000000d79b01002f29000000000000e19b01003029000000000000eb9b01003129000000000000f59b01003229000000000000ff9b01003329000000000000099c01003429000000000000139c010035290000000000001d9c01003629000000000000279c01003729000000000000319c010038290000000000003b9c01003929000000000000459c01003a290000000000004f9c01003b29000000000000599c01003c29000000000000639c01003d290000000000006d9c01003e29000000000000779c01003f29000000000000819c010040290000000000008b9c01004129000000000000959c010042290000000000009f9c01004329000000000000a99c01004429000000000000b39c01004529000000000000bd9c01004629000000000000c79c01004729000000000000d19c01004829000000000000db9c01004929000000000000e59c01004a29000000000000ef9c01004b29000000000000f99c01004c29000000000000039d01004d290000000000000d9d01004e29000000000000179d01004f29000000000000219d010050290000000000002b9d01005129000000000000359d010052290000000000003f9d01005329000000000000499d01005429000000000000539d010055290000000000005d9d01005629000000000000679d01005729000000000000719d010058290000000000007b9d01005929000000000000859d01005a290000000000008f9d01005b29000000000000999d01005c29000000000000a39d01005d29000000000000ad9d01005e29000000000000b79d01005f29000000000000c19d01006029000000000000cb9d01006129000000000000d59d01006229000000000000df9d01006329000000000000e99d01006429000000000000f39d01006529000000000000fd9d01006629000000000000079e01006729000000000000119e010068290000000000001b9e01006929000000000000259e01006a290000000000002f9e01006b29000000000000399e01006c29000000000000439e01006d290000000000004d9e01006e29000000000000579e01006f29000000000000619e010070290000000000006b9e01007129000000000000759e010072290000000000007f9e01007329000000000000899e01007429000000000000939e010075290000000000009d9e01007629000000000000a79e01007729000000000000b19e01007829000000000000bb9e01007929000000000000c59e01007a29000000000000cf9e01007b29000000000000d99e01007c29000000000000e39e01007d29000000000000ed9e01007e29000000000000f79e01007f29000000000000019f010080290000000000000b9f01008129000000000000159f010082290000000000001f9f01008329000000000000299f01008429000000000000339f010085290000000000003d9f01008629000000000000479f01008729000000000000519f010088290000000000005b9f01008929000000000000659f01008a290000000000006f9f01008b29000000000000799f01008c29000000000000839f01008d290000000000008d9f01008e29000000000000979f01008f29000000000000a19f01009029000000000000ab9f01009129000000000000b59f01009229000000000000bf9f01009329000000000000c99f01009429000000000000d39f01009529000000000000dd9f01009629000000000000e79f01009729000000000000f19f01009829000000000000fb9f0100992900000000000005a001009a290000000000000fa001009b2900000000000019a001009c2900000000000023a001009d290000000000002da001009e2900000000000037a001009f2900000000000041a00100a0290000000000004ba00100a12900000000000055a00100a2290000000000005fa00100a32900000000000069a00100a42900000000000073a00100a5290000000000007da00100a62900000000000087a00100a72900000000000091a00100a8290000000000009ba00100a929000000000000a5a00100aa29000000000000afa00100ab29000000000000b9a00100ac29000000000000c3a00100ad29000000000000cda00100ae29000000000000d7a00100af29000000000000e1a00100b029000000000000eba00100b129000000000000f5a00100b229000000000000ffa00100b32900000000000009a10100b42900000000000013a10100b5290000000000001da10100b62900000000000027a10100b72900000000000031a10100b8290000000000003ba10100b92900000000000045a10100ba290000000000004fa10100bb2900000000000059a10100bc2900000000000063a10100bd290000000000006da10100be2900000000000077a10100bf2900000000000081a10100c0290000000000008ba10100c12900000000000095a10100c2290000000000009fa10100c329000000000000a9a10100c429000000000000b3a10100c529000000000000bda10100c629000000000000c7a10100c729000000000000d1a10100c829000000000000dba10100c929000000000000e5a10100ca29000000000000efa10100cb29000000000000f9a10100cc2900000000000003a20100cd290000000000000da20100ce2900000000000017a20100cf2900000000000021a20100d0290000000000002ba20100d12900000000000035a20100d2290000000000003fa20100d32900000000000049a20100d42900000000000053a20100d5290000000000005da20100d62900000000000067a20100d72900000000000071a20100d8290000000000007ba20100d92900000000000085a20100da290000000000008fa20100db2900000000000099a20100dc29000000000000a3a20100dd29000000000000ada20100de29000000000000b7a20100df29000000000000c1a20100e029000000000000cba20100e129000000000000d5a20100e229000000000000dfa20100e329000000000000e9a20100e429000000000000f3a20100e529000000000000fda20100e62900000000000007a30100e72900000000000011a30100e8290000000000001ba30100e92900000000000025a30100ea290000000000002fa30100eb2900000000000039a30100ec2900000000000043a30100ed290000000000004da30100ee2900000000000057a30100ef2900000000000061a30100f0290000000000006ba30100f12900000000000075a30100f2290000000000007fa30100f32900000000000089a30100f42900000000000093a30100f5290000000000009da30100f629000000000000a7a30100f729000000000000b1a30100f829000000000000bba30100f929000000000000c5a30100fa29000000000000cfa30100fb29000000000000d9a30100fc29000000000000e3a30100fd29000000000000eda30100fe29000000000000f7a30100ff2900000000000001a40100002a0000000000000ba40100012a00000000000015a40100022a0000000000001fa40100032a00000000000029a40100042a00000000000033a40100052a0000000000003da40100062a00000000000047a40100072a00000000000051a40100082a0000000000005ba40100092a00000000000065a401000a2a0000000000006fa401000b2a00000000000079a401000c2a00000000000083a401000d2a0000000000008da401000e2a00000000000097a401000f2a000000000000a1a40100102a000000000000aba40100112a000000000000b5a40100122a000000000000bfa40100132a000000000000c9a40100142a000000000000d3a40100152a000000000000dda40100162a000000000000e7a40100172a000000000000f1a40100182a000000000000fba40100192a00000000000005a501001a2a0000000000000fa501001b2a00000000000019a501001c2a00000000000023a501001d2a0000000000002da501001e2a00000000000037a501001f2a00000000000041a50100202a0000000000004ba50100212a00000000000055a50100222a0000000000005fa50100232a00000000000069a50100242a00000000000073a50100252a0000000000007da50100262a00000000000087a50100272a00000000000091a50100282a0000000000009ba50100292a000000000000a5a501002a2a000000000000afa501002b2a000000000000b9a501002c2a000000000000c3a501002d2a000000000000cda501002e2a000000000000d7a501002f2a000000000000e1a50100302a000000000000eba50100312a000000000000f5a50100322a000000000000ffa50100332a00000000000009a60100342a00000000000013a60100352a0000000000001da60100362a00000000000027a60100372a00000000000031a60100382a0000000000003ba60100392a00000000000045a601003a2a0000000000004fa601003b2a00000000000059a601003c2a00000000000063a601003d2a0000000000006da601003e2a00000000000077a601003f2a00000000000081a60100402a0000000000008ba60100412a00000000000095a60100422a0000000000009fa60100432a000000000000a9a60100442a000000000000b3a60100452a000000000000bda60100462a000000000000c7a60100472a000000000000d1a60100482a000000000000dba60100492a000000000000e5a601004a2a000000000000efa601004b2a000000000000f9a601004c2a00000000000003a701004d2a0000000000000da701004e2a00000000000017a701004f2a00000000000021a70100502a0000000000002ba70100512a00000000000035a70100522a0000000000003fa70100532a00000000000049a70100542a00000000000053a70100552a0000000000005da70100562a00000000000067a70100572a00000000000071a70100582a0000000000007ba70100592a00000000000085a701005a2a0000000000008fa701005b2a00000000000099a701005c2a000000000000a3a701005d2a000000000000ada701005e2a000000000000b7a701005f2a000000000000c1a70100602a000000000000cba70100612a000000000000d5a70100622a000000000000dfa70100632a000000000000e9a70100642a000000000000f3a70100652a000000000000fda70100662a00000000000007a80100672a00000000000011a80100682a0000000000001ba80100692a00000000000025a801006a2a0000000000002fa801006b2a00000000000039a801006c2a00000000000043a801006d2a0000000000004da801006e2a00000000000057a801006f2a00000000000061a80100702a0000000000006ba80100712a00000000000075a80100722a0000000000007fa80100732a00000000000089a80100742a00000000000093a80100752a0000000000009da80100762a000000000000a7a80100772a000000000000b1a80100782a000000000000bba80100792a000000000000c5a801007a2a000000000000cfa801007b2a000000000000d9a801007c2a000000000000e3a801007d2a000000000000eda801007e2a000000000000f7a801007f2a00000000000001a90100802a0000000000000ba90100812a00000000000015a90100822a0000000000001fa90100832a00000000000029a90100842a00000000000033a90100852a0000000000003da90100862a00000000000047a90100872a00000000000051a90100882a0000000000005ba90100892a00000000000065a901008a2a0000000000006fa901008b2a00000000000079a901008c2a00000000000083a901008d2a0000000000008da901008e2a00000000000097a901008f2a000000000000a1a90100902a000000000000aba90100912a000000000000b5a90100922a000000000000bfa90100932a000000000000c9a90100942a000000000000d3a90100952a000000000000dda90100962a000000000000e7a90100972a000000000000f1a90100982a000000000000fba90100992a00000000000005aa01009a2a0000000000000faa01009b2a00000000000019aa01009c2a00000000000023aa01009d2a0000000000002daa01009e2a00000000000037aa01009f2a00000000000041aa0100a02a0000000000004baa0100a12a00000000000055aa0100a22a0000000000005faa0100a32a00000000000069aa0100a42a00000000000073aa0100a52a0000000000007daa0100a62a00000000000087aa0100a72a00000000000091aa0100a82a0000000000009baa0100a92a000000000000a5aa0100aa2a000000000000afaa0100ab2a000000000000b9aa0100ac2a000000000000c3aa0100ad2a000000000000cdaa0100ae2a000000000000d7aa0100af2a000000000000e1aa0100b02a000000000000ebaa0100b12a000000000000f5aa0100b22a000000000000ffaa0100b32a00000000000009ab0100b42a00000000000013ab0100b52a0000000000001dab0100b62a00000000000027ab0100b72a00000000000031ab0100b82a0000000000003bab0100b92a00000000000045ab0100ba2a0000000000004fab0100bb2a00000000000059ab0100bc2a00000000000063ab0100bd2a0000000000006dab0100be2a00000000000077ab0100bf2a00000000000081ab0100c02a0000000000008bab0100c12a00000000000095ab0100c22a0000000000009fab0100c32a000000000000a9ab0100c42a000000000000b3ab0100c52a000000000000bdab0100c62a000000000000c7ab0100c72a000000000000d1ab0100c82a000000000000dbab0100c92a000000000000e5ab0100ca2a000000000000efab0100cb2a000000000000f9ab0100cc2a00000000000003ac0100cd2a0000000000000dac0100ce2a00000000000017ac0100cf2a00000000000021ac0100d02a0000000000002bac0100d12a00000000000035ac0100d22a0000000000003fac0100d32a00000000000049ac0100d42a00000000000053ac0100d52a0000000000005dac0100d62a00000000000067ac0100d72a00000000000071ac0100d82a0000000000007bac0100d92a00000000000085ac0100da2a0000000000008fac0100db2a00000000000099ac0100dc2a000000000000a3ac0100dd2a000000000000adac0100de2a000000000000b7ac0100df2a000000000000c1ac0100e02a000000000000cbac0100e12a000000000000d5ac0100e22a000000000000dfac0100e32a000000000000e9ac0100e42a000000000000f3ac0100e52a000000000000fdac0100e62a00000000000007ad0100e72a00000000000011ad0100e82a0000000000001bad0100e92a00000000000025ad0100ea2a0000000000002fad0100eb2a00000000000039ad0100ec2a00000000000043ad0100ed2a0000000000004dad0100ee2a00000000000057ad0100ef2a00000000000061ad0100f02a0000000000006bad0100f12a00000000000075ad0100f22a0000000000007fad0100f32a00000000000089ad0100f42a00000000000093ad0100f52a0000000000009dad0100f62a000000000000a7ad0100f72a000000000000b1ad0100f82a000000000000bbad0100f92a000000000000c5ad0100fa2a000000000000cfad0100fb2a000000000000d9ad0100fc2a000000000000e3ad0100fd2a000000000000edad0100fe2a000000000000f7ad0100ff2a00000000000001ae0100002b0000000000000bae0100012b00000000000015ae0100022b0000000000001fae0100032b00000000000029ae0100042b00000000000033ae0100052b0000000000003dae0100062b00000000000047ae0100072b00000000000051ae0100082b0000000000005bae0100092b00000000000065ae01000a2b0000000000006fae01000b2b00000000000079ae01000c2b00000000000083ae01000d2b0000000000008dae01000e2b00000000000097ae01000f2b000000000000a1ae0100102b000000000000abae0100112b000000000000b5ae0100122b000000000000bfae0100132b000000000000c9ae0100142b000000000000d3ae0100152b000000000000ddae0100162b000000000000e7ae0100172b000000000000f1ae0100182b000000000000fbae0100192b00000000000005af01001a2b0000000000000faf01001b2b00000000000019af01001c2b00000000000023af01001d2b0000000000002daf01001e2b00000000000037af01001f2b00000000000041af0100202b0000000000004baf0100212b00000000000055af0100222b0000000000005faf0100232b00000000000069af0100242b00000000000073af0100252b0000000000007daf0100262b00000000000087af0100272b00000000000091af0100282b0000000000009baf0100292b000000000000a5af01002a2b000000000000afaf01002b2b000000000000b9af01002c2b000000000000c3af01002d2b000000000000cdaf01002e2b000000000000d7af01002f2b000000000000e1af0100302b000000000000ebaf0100312b000000000000f5af0100322b000000000000ffaf0100332b00000000000009b00100342b00000000000013b00100352b0000000000001db00100362b00000000000027b00100372b00000000000031b00100382b0000000000003bb00100392b00000000000045b001003a2b0000000000004fb001003b2b00000000000059b001003c2b00000000000063b001003d2b0000000000006db001003e2b00000000000077b001003f2b00000000000081b00100402b0000000000008bb00100412b00000000000095b00100422b0000000000009fb00100432b000000000000a9b00100442b000000000000b3b00100452b000000000000bdb00100462b000000000000c7b00100472b000000000000d1b00100482b000000000000dbb00100492b000000000000e5b001004a2b000000000000efb001004b2b000000000000f9b001004c2b00000000000003b101004d2b0000000000000db101004e2b00000000000017b101004f2b00000000000021b10100502b0000000000002bb10100512b00000000000035b10100522b0000000000003fb10100532b00000000000049b10100542b00000000000053b10100552b0000000000005db10100562b00000000000067b10100572b00000000000071b10100582b0000000000007bb10100592b00000000000085b101005a2b0000000000008fb101005b2b00000000000099b101005c2b000000000000a3b101005d2b000000000000adb101005e2b000000000000b7b101005f2b000000000000c1b10100602b000000000000cbb10100612b000000000000d5b10100622b000000000000dfb10100632b000000000000e9b10100642b000000000000f3b10100652b000000000000fdb10100662b00000000000007b20100672b00000000000011b20100682b0000000000001bb20100692b00000000000025b201006a2b0000000000002fb201006b2b00000000000039b201006c2b00000000000043b201006d2b0000000000004db201006e2b00000000000057b201006f2b00000000000061b20100702b0000000000006bb20100712b00000000000075b20100722b0000000000007fb20100732b00000000000089b20100742b00000000000093b20100752b0000000000009db20100762b000000000000a7b20100772b000000000000b1b20100782b000000000000bbb20100792b000000000000c5b201007a2b000000000000cfb201007b2b000000000000d9b201007c2b000000000000e3b201007d2b000000000000edb201007e2b000000000000f7b201007f2b00000000000001b30100802b0000000000000bb30100812b00000000000015b30100822b0000000000001fb30100832b00000000000029b30100842b00000000000033b30100852b0000000000003db30100862b00000000000047b30100872b00000000000051b30100882b0000000000005bb30100892b00000000000065b301008a2b0000000000006fb301008b2b00000000000079b301008c2b00000000000083b301008d2b0000000000008db301008e2b00000000000097b301008f2b000000000000a1b30100902b000000000000abb30100912b000000000000b5b30100922b000000000000bfb30100932b000000000000c9b30100942b000000000000d3b30100952b000000000000ddb30100962b000000000000e7b30100972b000000000000f1b30100982b000000000000fbb30100992b00000000000005b401009a2b0000000000000fb401009b2b00000000000019b401009c2b00000000000023b401009d2b0000000000002db401009e2b00000000000037b401009f2b00000000000041b40100a02b0000000000004bb40100a12b00000000000055b40100a22b0000000000005fb40100a32b00000000000069b40100a42b00000000000073b40100a52b0000000000007db40100a62b00000000000087b40100a72b00000000000091b40100a82b0000000000009bb40100a92b000000000000a5b40100aa2b000000000000afb40100ab2b000000000000b9b40100ac2b000000000000c3b40100ad2b000000000000cdb40100ae2b000000000000d7b40100af2b000000000000e1b40100b02b000000000000ebb40100b12b000000000000f5b40100b22b000000000000ffb40100b32b00000000000009b50100b42b00000000000013b50100b52b0000000000001db50100b62b00000000000027b50100b72b00000000000031b50100b82b0000000000003bb50100b92b00000000000045b50100ba2b0000000000004fb50100bb2b00000000000059b50100bc2b00000000000063b50100bd2b0000000000006db50100be2b00000000000077b50100bf2b00000000000081b50100c02b0000000000008bb50100c12b00000000000095b50100c22b0000000000009fb50100c32b000000000000a9b50100c42b000000000000b3b50100c52b000000000000bdb50100c62b000000000000c7b50100c72b000000000000d1b50100c82b000000000000dbb50100c92b000000000000e5b50100ca2b000000000000efb50100cb2b000000000000f9b50100cc2b00000000000003b60100cd2b0000000000000db60100ce2b00000000000017b60100cf2b00000000000021b60100d02b0000000000002bb60100d12b00000000000035b60100d22b0000000000003fb60100d32b00000000000049b60100d42b00000000000053b60100d52b0000000000005db60100d62b00000000000067b60100d72b00000000000071b60100d82b0000000000007bb60100d92b00000000000085b60100da2b0000000000008fb60100db2b00000000000099b60100dc2b000000000000a3b60100dd2b000000000000adb60100de2b000000000000b7b60100df2b000000000000c1b60100e02b000000000000cbb60100e12b000000000000d5b60100e22b000000000000dfb60100e32b000000000000e9b60100e42b000000000000f3b60100e52b000000000000fdb60100e62b00000000000007b70100e72b00000000000011b70100e82b0000000000001bb70100e92b00000000000025b70100ea2b0000000000002fb70100eb2b00000000000039b70100ec2b00000000000043b70100ed2b0000000000004db70100ee2b00000000000057b70100ef2b00000000000061b70100f02b0000000000006bb70100f12b00000000000075b70100f22b0000000000007fb70100f32b00000000000089b70100f42b00000000000093b70100f52b0000000000009db70100f62b000000000000a7b70100f72b000000000000b1b70100f82b000000000000bbb70100f92b000000000000c5b70100fa2b000000000000cfb70100fb2b000000000000d9b70100fc2b000000000000e3b70100fd2b000000000000edb70100fe2b000000000000f7b70100ff2b00000000000001b80100002c0000000000000bb80100012c00000000000015b80100022c0000000000001fb80100032c00000000000029b80100042c00000000000033b80100052c0000000000003db80100062c00000000000047b80100072c00000000000051b80100082c0000000000005bb80100092c00000000000065b801000a2c0000000000006fb801000b2c00000000000079b801000c2c00000000000083b801000d2c0000000000008db801000e2c00000000000097b801000f2c000000000000a1b80100102c000000000000abb80100112c000000000000b5b80100122c000000000000bfb80100132c000000000000c9b80100142c000000000000d3b80100152c000000000000ddb80100162c000000000000e7b80100172c000000000000f1b80100182c000000000000fbb80100192c00000000000005b901001a2c0000000000000fb901001b2c00000000000019b901001c2c00000000000023b901001d2c0000000000002db901001e2c00000000000037b901001f2c00000000000041b90100202c0000000000004bb90100212c00000000000055b90100222c0000000000005fb90100232c00000000000069b90100242c00000000000073b90100252c0000000000007db90100262c00000000000087b90100272c00000000000091b90100282c0000000000009bb90100292c000000000000a5b901002a2c000000000000afb901002b2c000000000000b9b901002c2c000000000000c3b901002d2c000000000000cdb901002e2c000000000000d7b901002f2c000000000000e1b90100302c000000000000ebb90100312c000000000000f5b90100322c000000000000ffb90100332c00000000000009ba0100342c00000000000013ba0100352c0000000000001dba0100362c00000000000027ba0100372c00000000000031ba0100382c0000000000003bba0100392c00000000000045ba01003a2c0000000000004fba01003b2c00000000000059ba01003c2c00000000000063ba01003d2c0000000000006dba01003e2c00000000000077ba01003f2c00000000000081ba0100402c0000000000008bba0100412c00000000000095ba0100422c0000000000009fba0100432c000000000000a9ba0100442c000000000000b3ba0100452c000000000000bdba0100462c000000000000c7ba0100472c000000000000d1ba0100482c000000000000dbba0100492c000000000000e5ba01004a2c000000000000efba01004b2c000000000000f9ba01004c2c00000000000003bb01004d2c0000000000000dbb01004e2c00000000000017bb01004f2c00000000000021bb0100502c0000000000002bbb0100512c00000000000035bb0100522c0000000000003fbb0100532c00000000000049bb0100542c00000000000053bb0100552c0000000000005dbb0100562c00000000000067bb0100572c00000000000071bb0100582c0000000000007bbb0100592c00000000000085bb01005a2c0000000000008fbb01005b2c00000000000099bb01005c2c000000000000a3bb01005d2c000000000000adbb01005e2c000000000000b7bb01005f2c000000000000c1bb0100602c000000000000cbbb0100612c000000000000d5bb0100622c000000000000dfbb0100632c000000000000e9bb0100642c000000000000f3bb0100652c000000000000fdbb0100662c00000000000007bc0100672c00000000000011bc0100682c0000000000001bbc0100692c00000000000025bc01006a2c0000000000002fbc01006b2c00000000000039bc01006c2c00000000000043bc01006d2c0000000000004dbc01006e2c00000000000057bc01006f2c00000000000061bc0100702c0000000000006bbc0100712c00000000000075bc0100722c0000000000007fbc0100732c00000000000089bc0100742c00000000000093bc0100752c0000000000009dbc0100762c000000000000a7bc0100772c000000000000b1bc0100782c000000000000bbbc0100792c000000000000c5bc01007a2c000000000000cfbc01007b2c000000000000d9bc01007c2c000000000000e3bc01007d2c000000000000edbc01007e2c000000000000f7bc01007f2c00000000000001bd0100802c0000000000000bbd0100812c00000000000015bd0100822c0000000000001fbd0100832c00000000000029bd0100842c00000000000033bd0100852c0000000000003dbd0100862c00000000000047bd0100872c00000000000051bd0100882c0000000000005bbd0100892c00000000000065bd01008a2c0000000000006fbd01008b2c00000000000079bd01008c2c00000000000083bd01008d2c0000000000008dbd01008e2c00000000000097bd01008f2c000000000000a1bd0100902c000000000000abbd0100912c000000000000b5bd0100922c000000000000bfbd0100932c000000000000c9bd0100942c000000000000d3bd0100952c000000000000ddbd0100962c000000000000e7bd0100972c000000000000f1bd0100982c000000000000fbbd0100992c00000000000005be01009a2c0000000000000fbe01009b2c00000000000019be01009c2c00000000000023be01009d2c0000000000002dbe01009e2c00000000000037be01009f2c00000000000041be0100a02c0000000000004bbe0100a12c00000000000055be0100a22c0000000000005fbe0100a32c00000000000069be0100a42c00000000000073be0100a52c0000000000007dbe0100a62c00000000000087be0100a72c00000000000091be0100a82c0000000000009bbe0100a92c000000000000a5be0100aa2c000000000000afbe0100ab2c000000000000b9be0100ac2c000000000000c3be0100ad2c000000000000cdbe0100ae2c000000000000d7be0100af2c000000000000e1be0100b02c000000000000ebbe0100b12c000000000000f5be0100b22c000000000000ffbe0100b32c00000000000009bf0100b42c00000000000013bf0100b52c0000000000001dbf0100b62c00000000000027bf0100b72c00000000000031bf0100b82c0000000000003bbf0100b92c00000000000045bf0100ba2c0000000000004fbf0100bb2c00000000000059bf0100bc2c00000000000063bf0100bd2c0000000000006dbf0100be2c00000000000077bf0100bf2c00000000000081bf0100c02c0000000000008bbf0100c12c00000000000095bf0100c22c0000000000009fbf0100c32c000000000000a9bf0100c42c000000000000b3bf0100c52c000000000000bdbf0100c62c000000000000c7bf0100c72c000000000000d1bf0100c82c000000000000dbbf0100c92c000000000000e5bf0100ca2c000000000000efbf0100cb2c000000000000f9bf0100cc2c00000000000003c00100cd2c0000000000000dc00100ce2c00000000000017c00100cf2c00000000000021c00100d02c0000000000002bc00100d12c00000000000035c00100d22c0000000000003fc00100d32c00000000000049c00100d42c00000000000053c00100d52c0000000000005dc00100d62c00000000000067c00100d72c00000000000071c00100d82c0000000000007bc00100d92c00000000000085c00100da2c0000000000008fc00100db2c00000000000099c00100dc2c000000000000a3c00100dd2c000000000000adc00100de2c000000000000b7c00100df2c000000000000c1c00100e02c000000000000cbc00100e12c000000000000d5c00100e22c000000000000dfc00100e32c000000000000e9c00100e42c000000000000f3c00100e52c000000000000fdc00100e62c00000000000007c10100e72c00000000000011c10100e82c0000000000001bc10100e92c00000000000025c10100ea2c0000000000002fc10100eb2c00000000000039c10100ec2c00000000000043c10100ed2c0000000000004dc10100ee2c00000000000057c10100ef2c00000000000061c10100f02c0000000000006bc10100f12c00000000000075c10100f22c0000000000007fc10100f32c00000000000089c10100f42c00000000000093c10100f52c0000000000009dc10100f62c000000000000a7c10100f72c000000000000b1c10100f82c000000000000bbc10100f92c000000000000c5c10100fa2c000000000000cfc10100fb2c000000000000d9c10100fc2c000000000000e3c10100fd2c000000000000edc10100fe2c000000000000f7c10100ff2c00000000000001c20100002d0000000000000bc20100012d00000000000015c20100022d0000000000001fc20100032d00000000000029c20100042d00000000000033c20100052d0000000000003dc20100062d00000000000047c20100072d00000000000051c20100082d0000000000005bc20100092d00000000000065c201000a2d0000000000006fc201000b2d00000000000079c201000c2d00000000000083c201000d2d0000000000008dc201000e2d00000000000097c201000f2d000000000000a1c20100102d000000000000abc20100112d000000000000b5c20100122d000000000000bfc20100132d000000000000c9c20100142d000000000000d3c20100152d000000000000ddc20100162d000000000000e7c20100172d000000000000f1c20100182d000000000000fbc20100192d00000000000005c301001a2d0000000000000fc301001b2d00000000000019c301001c2d00000000000023c301001d2d0000000000002dc301001e2d00000000000037c301001f2d00000000000041c30100202d0000000000004bc30100212d00000000000055c30100222d0000000000005fc30100232d00000000000069c30100242d00000000000073c30100252d0000000000007dc30100262d00000000000087c30100272d00000000000091c30100282d0000000000009bc30100292d000000000000a5c301002a2d000000000000afc301002b2d000000000000b9c301002c2d000000000000c3c301002d2d000000000000cdc301002e2d000000000000d7c301002f2d000000000000e1c30100302d000000000000ebc30100312d000000000000f5c30100322d000000000000ffc30100332d00000000000009c40100342d00000000000013c40100352d0000000000001dc40100362d00000000000027c40100372d00000000000031c40100382d0000000000003bc40100392d00000000000045c401003a2d0000000000004fc401003b2d00000000000059c401003c2d00000000000063c401003d2d0000000000006dc401003e2d00000000000077c401003f2d00000000000081c40100402d0000000000008bc40100412d00000000000095c40100422d0000000000009fc40100432d000000000000a9c40100442d000000000000b3c40100452d000000000000bdc40100462d000000000000c7c40100472d000000000000d1c40100482d000000000000dbc40100492d000000000000e5c401004a2d000000000000efc401004b2d000000000000f9c401004c2d00000000000003c501004d2d0000000000000dc501004e2d00000000000017c501004f2d00000000000021c50100502d0000000000002bc50100512d00000000000035c50100522d0000000000003fc50100532d00000000000049c50100542d00000000000053c50100552d0000000000005dc50100562d00000000000067c50100572d00000000000071c50100582d0000000000007bc50100592d00000000000085c501005a2d0000000000008fc501005b2d00000000000099c501005c2d000000000000a3c501005d2d000000000000adc501005e2d000000000000b7c501005f2d000000000000c1c50100602d000000000000cbc50100612d000000000000d5c50100622d000000000000dfc50100632d000000000000e9c50100642d000000000000f3c50100652d000000000000fdc50100662d00000000000007c60100672d00000000000011c60100682d0000000000001bc60100692d00000000000025c601006a2d0000000000002fc601006b2d00000000000039c601006c2d00000000000043c601006d2d0000000000004dc601006e2d00000000000057c601006f2d00000000000061c60100702d0000000000006bc60100712d00000000000075c60100722d0000000000007fc60100732d00000000000089c60100742d00000000000093c60100752d0000000000009dc60100762d000000000000a7c60100772d000000000000b1c60100782d000000000000bbc60100792d000000000000c5c601007a2d000000000000cfc601007b2d000000000000d9c601007c2d000000000000e3c601007d2d000000000000edc601007e2d000000000000f7c601007f2d00000000000001c70100802d0000000000000bc70100812d00000000000015c70100822d0000000000001fc70100832d00000000000029c70100842d00000000000033c70100852d0000000000003dc70100862d00000000000047c70100872d00000000000051c70100882d0000000000005bc70100892d00000000000065c701008a2d0000000000006fc701008b2d00000000000079c701008c2d00000000000083c701008d2d0000000000008dc701008e2d00000000000097c701008f2d000000000000a1c70100902d000000000000abc70100912d000000000000b5c70100922d000000000000bfc70100932d000000000000c9c70100942d000000000000d3c70100952d000000000000ddc70100962d000000000000e7c70100972d000000000000f1c70100982d000000000000fbc70100992d00000000000005c801009a2d0000000000000fc801009b2d00000000000019c801009c2d00000000000023c801009d2d0000000000002dc801009e2d00000000000037c801009f2d00000000000041c80100a02d0000000000004bc80100a12d00000000000055c80100a22d0000000000005fc80100a32d00000000000069c80100a42d00000000000073c80100a52d0000000000007dc80100a62d00000000000087c80100a72d00000000000091c80100a82d0000000000009bc80100a92d000000000000a5c80100aa2d000000000000afc80100ab2d000000000000b9c80100ac2d000000000000c3c80100ad2d000000000000cdc80100ae2d000000000000d7c80100af2d000000000000e1c80100b02d000000000000ebc80100b12d000000000000f5c80100b22d000000000000ffc80100b32d00000000000009c90100b42d00000000000013c90100b52d0000000000001dc90100b62d00000000000027c90100b72d00000000000031c90100b82d0000000000003bc90100b92d00000000000045c90100ba2d0000000000004fc90100bb2d00000000000059c90100bc2d00000000000063c90100bd2d0000000000006dc90100be2d00000000000077c90100bf2d00000000000081c90100c02d0000000000008bc90100c12d00000000000095c90100c22d0000000000009fc90100c32d000000000000a9c90100c42d000000000000b3c90100c52d000000000000bdc90100c62d000000000000c7c90100c72d000000000000d1c90100c82d000000000000dbc90100c92d000000000000e5c90100ca2d000000000000efc90100cb2d000000000000f9c90100cc2d00000000000003ca0100cd2d0000000000000dca0100ce2d00000000000017ca0100cf2d00000000000021ca0100d02d0000000000002bca0100d12d00000000000035ca0100d22d0000000000003fca0100d32d00000000000049ca0100d42d00000000000053ca0100d52d0000000000005dca0100d62d00000000000067ca0100d72d00000000000071ca0100d82d0000000000007bca0100d92d00000000000085ca0100da2d0000000000008fca0100db2d00000000000099ca0100dc2d000000000000a3ca0100dd2d000000000000adca0100de2d000000000000b7ca0100df2d000000000000c1ca0100e02d000000000000cbca0100e12d000000000000d5ca0100e22d000000000000dfca0100e32d000000000000e9ca0100e42d000000000000f3ca0100e52d000000000000fdca0100e62d00000000000007cb0100e72d00000000000011cb0100e82d0000000000001bcb0100e92d00000000000025cb0100ea2d0000000000002fcb0100eb2d00000000000039cb0100ec2d00000000000043cb0100ed2d0000000000004dcb0100ee2d00000000000057cb0100ef2d00000000000061cb0100f02d0000000000006bcb0100f12d00000000000075cb0100f22d0000000000007fcb0100f32d00000000000089cb0100f42d00000000000093cb0100f52d0000000000009dcb0100f62d000000000000a7cb0100f72d000000000000b1cb0100f82d000000000000bbcb0100f92d000000000000c5cb0100fa2d000000000000cfcb0100fb2d000000000000d9cb0100fc2d000000000000e3cb0100fd2d000000000000edcb0100fe2d000000000000f7cb0100ff2d00000000000001cc0100002e0000000000000bcc0100012e00000000000015cc0100022e0000000000001fcc0100032e00000000000029cc0100042e00000000000033cc0100052e0000000000003dcc0100062e00000000000047cc0100072e00000000000051cc0100082e0000000000005bcc0100092e00000000000065cc01000a2e0000000000006fcc01000b2e00000000000079cc01000c2e00000000000083cc01000d2e0000000000008dcc01000e2e00000000000097cc01000f2e000000000000a1cc0100102e000000000000abcc0100112e000000000000b5cc0100122e000000000000bfcc0100132e000000000000c9cc0100142e000000000000d3cc0100152e000000000000ddcc0100162e000000000000e7cc0100172e000000000000f1cc0100182e000000000000fbcc0100192e00000000000005cd01001a2e0000000000000fcd01001b2e00000000000019cd01001c2e00000000000023cd01001d2e0000000000002dcd01001e2e00000000000037cd01001f2e00000000000041cd0100202e0000000000004bcd0100212e00000000000055cd0100222e0000000000005fcd0100232e00000000000069cd0100242e00000000000073cd0100252e0000000000007dcd0100262e00000000000087cd0100272e00000000000091cd0100282e0000000000009bcd0100292e000000000000a5cd01002a2e000000000000afcd01002b2e000000000000b9cd01002c2e000000000000c3cd01002d2e000000000000cdcd01002e2e000000000000d7cd01002f2e000000000000e1cd0100302e000000000000ebcd0100312e000000000000f5cd0100322e000000000000ffcd0100332e00000000000009ce0100342e00000000000013ce0100352e0000000000001dce0100362e00000000000027ce0100372e00000000000031ce0100382e0000000000003bce0100392e00000000000045ce01003a2e0000000000004fce01003b2e00000000000059ce01003c2e00000000000063ce01003d2e0000000000006dce01003e2e00000000000077ce01003f2e00000000000081ce0100402e0000000000008bce0100412e00000000000095ce0100422e0000000000009fce0100432e000000000000a9ce0100442e000000000000b3ce0100452e000000000000bdce0100462e000000000000c7ce0100472e000000000000d1ce0100482e000000000000dbce0100492e000000000000e5ce01004a2e000000000000efce01004b2e000000000000f9ce01004c2e00000000000003cf01004d2e0000000000000dcf01004e2e00000000000017cf01004f2e00000000000021cf0100502e0000000000002bcf0100512e00000000000035cf0100522e0000000000003fcf0100532e00000000000049cf0100542e00000000000053cf0100552e0000000000005dcf0100562e00000000000067cf0100572e00000000000071cf0100582e0000000000007bcf0100592e00000000000085cf01005a2e0000000000008fcf01005b2e00000000000099cf01005c2e000000000000a3cf01005d2e000000000000adcf01005e2e000000000000b7cf01005f2e000000000000c1cf0100602e000000000000cbcf0100612e000000000000d5cf0100622e000000000000dfcf0100632e000000000000e9cf0100642e000000000000f3cf0100652e000000000000fdcf0100662e00000000000007d00100672e00000000000011d00100682e0000000000001bd00100692e00000000000025d001006a2e0000000000002fd001006b2e00000000000039d001006c2e00000000000043d001006d2e0000000000004dd001006e2e00000000000057d001006f2e00000000000061d00100702e0000000000006bd00100712e00000000000075d00100722e0000000000007fd00100732e00000000000089d00100742e00000000000093d00100752e0000000000009dd00100762e000000000000a7d00100772e000000000000b1d00100782e000000000000bbd00100792e000000000000c5d001007a2e000000000000cfd001007b2e000000000000d9d001007c2e000000000000e3d001007d2e000000000000edd001007e2e000000000000f7d001007f2e00000000000001d10100802e0000000000000bd10100812e00000000000015d10100822e0000000000001fd10100832e00000000000029d10100842e00000000000033d10100852e0000000000003dd10100862e00000000000047d10100872e00000000000051d10100882e0000000000005bd10100892e00000000000065d101008a2e0000000000006fd101008b2e00000000000079d101008c2e00000000000083d101008d2e0000000000008dd101008e2e00000000000097d101008f2e000000000000a1d10100902e000000000000abd10100912e000000000000b5d10100922e000000000000bfd10100932e000000000000c9d10100942e000000000000d3d10100952e000000000000ddd10100962e000000000000e7d10100972e000000000000f1d10100982e000000000000fbd10100992e00000000000005d201009a2e0000000000000fd201009b2e00000000000019d201009c2e00000000000023d201009d2e0000000000002dd201009e2e00000000000037d201009f2e00000000000041d20100a02e0000000000004bd20100a12e00000000000055d20100a22e0000000000005fd20100a32e00000000000069d20100a42e00000000000073d20100a52e0000000000007dd20100a62e00000000000087d20100a72e00000000000091d20100a82e0000000000009bd20100a92e000000000000a5d20100aa2e000000000000afd20100ab2e000000000000b9d20100ac2e000000000000c3d20100ad2e000000000000cdd20100ae2e000000000000d7d20100af2e000000000000e1d20100b02e000000000000ebd20100b12e000000000000f5d20100b22e000000000000ffd20100b32e00000000000009d30100b42e00000000000013d30100b52e0000000000001dd30100b62e00000000000027d30100b72e00000000000031d30100b82e0000000000003bd30100b92e00000000000045d30100ba2e0000000000004fd30100bb2e00000000000059d30100bc2e00000000000063d30100bd2e0000000000006dd30100be2e00000000000077d30100bf2e00000000000081d30100c02e0000000000008bd30100c12e00000000000095d30100c22e0000000000009fd30100c32e000000000000a9d30100c42e000000000000b3d30100c52e000000000000bdd30100c62e000000000000c7d30100c72e000000000000d1d30100c82e000000000000dbd30100c92e000000000000e5d30100ca2e000000000000efd30100cb2e000000000000f9d30100cc2e00000000000003d40100cd2e0000000000000dd40100ce2e00000000000017d40100cf2e00000000000021d40100d02e0000000000002bd40100d12e00000000000035d40100d22e0000000000003fd40100d32e00000000000049d40100d42e00000000000053d40100d52e0000000000005dd40100d62e00000000000067d40100d72e00000000000071d40100d82e0000000000007bd40100d92e00000000000085d40100da2e0000000000008fd40100db2e00000000000099d40100dc2e000000000000a3d40100dd2e000000000000add40100de2e000000000000b7d40100df2e000000000000c1d40100e02e000000000000cbd40100e12e000000000000d5d40100e22e000000000000dfd40100e32e000000000000e9d40100e42e000000000000f3d40100e52e000000000000fdd40100e62e00000000000007d50100e72e00000000000011d50100e82e0000000000001bd50100e92e00000000000025d50100ea2e0000000000002fd50100eb2e00000000000039d50100ec2e00000000000043d50100ed2e0000000000004dd50100ee2e00000000000057d50100ef2e00000000000061d50100f02e0000000000006bd50100f12e00000000000075d50100f22e0000000000007fd50100f32e00000000000089d50100f42e00000000000093d50100f52e0000000000009dd50100f62e000000000000a7d50100f72e000000000000b1d50100f82e000000000000bbd50100f92e000000000000c5d50100fa2e000000000000cfd50100fb2e000000000000d9d50100fc2e000000000000e3d50100fd2e000000000000edd50100fe2e000000000000f7d50100ff2e00000000000001d60100002f0000000000000bd60100012f00000000000015d60100022f0000000000001fd60100032f00000000000029d60100042f00000000000033d60100052f0000000000003dd60100062f00000000000047d60100072f00000000000051d60100082f0000000000005bd60100092f00000000000065d601000a2f0000000000006fd601000b2f00000000000079d601000c2f00000000000083d601000d2f0000000000008dd601000e2f00000000000097d601000f2f000000000000a1d60100102f000000000000abd60100112f000000000000b5d60100122f000000000000bfd60100132f000000000000c9d60100142f000000000000d3d60100152f000000000000ddd60100162f000000000000e7d60100172f000000000000f1d60100182f000000000000fbd60100192f00000000000005d701001a2f0000000000000fd701001b2f00000000000019d701001c2f00000000000023d701001d2f0000000000002dd701001e2f00000000000037d701001f2f00000000000041d70100202f0000000000004bd70100212f00000000000055d70100222f0000000000005fd70100232f00000000000069d70100242f00000000000073d70100252f0000000000007dd70100262f00000000000087d70100272f00000000000091d70100282f0000000000009bd70100292f000000000000a5d701002a2f000000000000afd701002b2f000000000000b9d701002c2f000000000000c3d701002d2f000000000000cdd701002e2f000000000000d7d701002f2f000000000000e1d70100302f000000000000ebd70100312f000000000000f5d70100322f000000000000ffd70100332f00000000000009d80100342f00000000000013d80100352f0000000000001dd80100362f00000000000027d80100372f00000000000031d80100382f0000000000003bd80100392f00000000000045d801003a2f0000000000004fd801003b2f00000000000059d801003c2f00000000000063d801003d2f0000000000006dd801003e2f00000000000077d801003f2f00000000000081d80100402f0000000000008bd80100412f00000000000095d80100422f0000000000009fd80100432f000000000000a9d80100442f000000000000b3d80100452f000000000000bdd80100462f000000000000c7d80100472f000000000000d1d80100482f000000000000dbd80100492f000000000000e5d801004a2f000000000000efd801004b2f000000000000f9d801004c2f00000000000003d901004d2f0000000000000dd901004e2f00000000000017d901004f2f00000000000021d90100502f0000000000002bd90100512f00000000000035d90100522f0000000000003fd90100532f00000000000049d90100542f00000000000053d90100552f0000000000005dd90100562f00000000000067d90100572f00000000000071d90100582f0000000000007bd90100592f00000000000085d901005a2f0000000000008fd901005b2f00000000000099d901005c2f000000000000a3d901005d2f000000000000add901005e2f000000000000b7d901005f2f000000000000c1d90100602f000000000000cbd90100612f000000000000d5d90100622f000000000000dfd90100632f000000000000e9d90100642f000000000000f3d90100652f000000000000fdd90100662f00000000000007da0100672f00000000000011da0100682f0000000000001bda0100692f00000000000025da01006a2f0000000000002fda01006b2f00000000000039da01006c2f00000000000043da01006d2f0000000000004dda01006e2f00000000000057da01006f2f00000000000061da0100702f0000000000006bda0100712f00000000000075da0100722f0000000000007fda0100732f00000000000089da0100742f00000000000093da0100752f0000000000009dda0100762f000000000000a7da0100772f000000000000b1da0100782f000000000000bbda0100792f000000000000c5da01007a2f000000000000cfda01007b2f000000000000d9da01007c2f000000000000e3da01007d2f000000000000edda01007e2f000000000000f7da01007f2f00000000000001db0100802f0000000000000bdb0100812f00000000000015db0100822f0000000000001fdb0100832f00000000000029db0100842f00000000000033db0100852f0000000000003ddb0100862f00000000000047db0100872f00000000000051db0100882f0000000000005bdb0100892f00000000000065db01008a2f0000000000006fdb01008b2f00000000000079db01008c2f00000000000083db01008d2f0000000000008ddb01008e2f00000000000097db01008f2f000000000000a1db0100902f000000000000abdb0100912f000000000000b5db0100922f000000000000bfdb0100932f000000000000c9db0100942f000000000000d3db0100952f000000000000dddb0100962f000000000000e7db0100972f000000000000f1db0100982f000000000000fbdb0100992f00000000000005dc01009a2f0000000000000fdc01009b2f00000000000019dc01009c2f00000000000023dc01009d2f0000000000002ddc01009e2f00000000000037dc01009f2f00000000000041dc0100a02f0000000000004bdc0100a12f00000000000055dc0100a22f0000000000005fdc0100a32f00000000000069dc0100a42f00000000000073dc0100a52f0000000000007ddc0100a62f00000000000087dc0100a72f00000000000091dc0100a82f0000000000009bdc0100a92f000000000000a5dc0100aa2f000000000000afdc0100ab2f000000000000b9dc0100ac2f000000000000c3dc0100ad2f000000000000cddc0100ae2f000000000000d7dc0100af2f000000000000e1dc0100b02f000000000000ebdc0100b12f000000000000f5dc0100b22f000000000000ffdc0100b32f00000000000009dd0100b42f00000000000013dd0100b52f0000000000001ddd0100b62f00000000000027dd0100b72f00000000000031dd0100b82f0000000000003bdd0100b92f00000000000045dd0100ba2f0000000000004fdd0100bb2f00000000000059dd0100bc2f00000000000063dd0100bd2f0000000000006ddd0100be2f00000000000077dd0100bf2f00000000000081dd0100c02f0000000000008bdd0100c12f00000000000095dd0100c22f0000000000009fdd0100c32f000000000000a9dd0100c42f000000000000b3dd0100c52f000000000000bddd0100c62f000000000000c7dd0100c72f000000000000d1dd0100c82f000000000000dbdd0100c92f000000000000e5dd0100ca2f000000000000efdd0100cb2f000000000000f9dd0100cc2f00000000000003de0100cd2f0000000000000dde0100ce2f00000000000017de0100cf2f00000000000021de0100d02f0000000000002bde0100d12f00000000000035de0100d22f0000000000003fde0100d32f00000000000049de0100d42f00000000000053de0100d52f0000000000005dde0100d62f00000000000067de0100d72f00000000000071de0100d82f0000000000007bde0100d92f00000000000085de0100da2f0000000000008fde0100db2f00000000000099de0100dc2f000000000000a3de0100dd2f000000000000adde0100de2f000000000000b7de0100df2f000000000000c1de0100e02f000000000000cbde0100e12f000000000000d5de0100e22f000000000000dfde0100e32f000000000000e9de0100e42f000000000000f3de0100e52f000000000000fdde0100e62f00000000000007df0100e72f00000000000011df0100e82f0000000000001bdf0100e92f00000000000025df0100ea2f0000000000002fdf0100eb2f00000000000039df0100ec2f00000000000043df0100ed2f0000000000004ddf0100ee2f00000000000057df0100ef2f00000000000061df0100f02f0000000000006bdf0100f12f00000000000075df0100f22f0000000000007fdf0100f32f00000000000089df0100f42f00000000000093df0100f52f0000000000009ddf0100f62f000000000000a7df0100f72f000000000000b1df0100f82f000000000000bbdf0100f92f000000000000c5df0100fa2f000000000000cfdf0100fb2f000000000000d9df0100fc2f000000000000e3df0100fd2f000000000000eddf0100fe2f000000000000f7df0100ff2f00000000000001e0010000300000000000000be00100013000000000000015e0010002300000000000001fe00100033000000000000029e00100043000000000000033e0010005300000000000003de00100063000000000000047e00100073000000000000051e0010008300000000000005be00100093000000000000065e001000a300000000000006fe001000b3000000000000079e001000c3000000000000083e001000d300000000000008de001000e3000000000000097e001000f30000000000000a1e001001030000000000000abe001001130000000000000b5e001001230000000000000bfe001001330000000000000c9e001001430000000000000d3e001001530000000000000dde001001630000000000000e7e001001730000000000000f1e001001830000000000000fbe00100193000000000000005e101001a300000000000000fe101001b3000000000000019e101001c3000000000000023e101001d300000000000002de101001e3000000000000037e101001f3000000000000041e1010020300000000000004be10100213000000000000055e1010022300000000000005fe10100233000000000000069e10100243000000000000073e1010025300000000000007de10100263000000000000087e10100273000000000000091e1010028300000000000009be101002930000000000000a5e101002a30000000000000afe101002b30000000000000b9e101002c30000000000000c3e101002d30000000000000cde101002e30000000000000d7e101002f30000000000000e1e101003030000000000000ebe101003130000000000000f5e101003230000000000000ffe10100333000000000000009e20100343000000000000013e2010035300000000000001de20100363000000000000027e20100373000000000000031e2010038300000000000003be20100393000000000000045e201003a300000000000004fe201003b3000000000000059e201003c3000000000000063e201003d300000000000006de201003e3000000000000077e201003f3000000000000081e2010040300000000000008be20100413000000000000095e2010042300000000000009fe201004330000000000000a9e201004430000000000000b3e201004530000000000000bde201004630000000000000c7e201004730000000000000d1e201004830000000000000dbe201004930000000000000e5e201004a30000000000000efe201004b30000000000000f9e201004c3000000000000003e301004d300000000000000de301004e3000000000000017e301004f3000000000000021e3010050300000000000002be30100513000000000000035e3010052300000000000003fe30100533000000000000049e30100543000000000000053e3010055300000000000005de30100563000000000000067e30100573000000000000071e3010058300000000000007be30100593000000000000085e301005a300000000000008fe301005b3000000000000099e301005c30000000000000a3e301005d30000000000000ade301005e30000000000000b7e301005f30000000000000c1e301006030000000000000cbe301006130000000000000d5e301006230000000000000dfe301006330000000000000e9e301006430000000000000f3e301006530000000000000fde30100663000000000000007e40100673000000000000011e4010068300000000000001be40100693000000000000025e401006a300000000000002fe401006b3000000000000039e401006c3000000000000043e401006d300000000000004de401006e3000000000000057e401006f3000000000000061e4010070300000000000006be40100713000000000000075e4010072300000000000007fe40100733000000000000089e40100743000000000000093e4010075300000000000009de401007630000000000000a7e401007730000000000000b1e401007830000000000000bbe401007930000000000000c5e401007a30000000000000cfe401007b30000000000000d9e401007c30000000000000e3e401007d30000000000000ede401007e30000000000000f7e401007f3000000000000001e5010080300000000000000be50100813000000000000015e5010082300000000000001fe50100833000000000000029e50100843000000000000033e5010085300000000000003de50100863000000000000047e50100873000000000000051e5010088300000000000005be50100893000000000000065e501008a300000000000006fe501008b3000000000000079e501008c3000000000000083e501008d300000000000008de501008e3000000000000097e501008f30000000000000a1e501009030000000000000abe501009130000000000000b5e501009230000000000000bfe501009330000000000000c9e501009430000000000000d3e501009530000000000000dde501009630000000000000e7e501009730000000000000f1e501009830000000000000fbe50100993000000000000005e601009a300000000000000fe601009b3000000000000019e601009c3000000000000023e601009d300000000000002de601009e3000000000000037e601009f3000000000000041e60100a0300000000000004be60100a13000000000000055e60100a2300000000000005fe60100a33000000000000069e60100a43000000000000073e60100a5300000000000007de60100a63000000000000087e60100a73000000000000091e60100a8300000000000009be60100a930000000000000a5e60100aa30000000000000afe60100ab30000000000000b9e60100ac30000000000000c3e60100ad30000000000000cde60100ae30000000000000d7e60100af30000000000000e1e60100b030000000000000ebe60100b130000000000000f5e60100b230000000000000ffe60100b33000000000000009e70100b43000000000000013e70100b5300000000000001de70100b63000000000000027e70100b73000000000000031e70100b8300000000000003be70100b93000000000000045e70100ba300000000000004fe70100bb3000000000000059e70100bc3000000000000063e70100bd300000000000006de70100be3000000000000077e70100bf3000000000000081e70100c0300000000000008be70100c13000000000000095e70100c2300000000000009fe70100c330000000000000a9e70100c430000000000000b3e70100c530000000000000bde70100c630000000000000c7e70100c730000000000000d1e70100c830000000000000dbe70100c930000000000000e5e70100ca30000000000000efe70100cb30000000000000f9e70100cc3000000000000003e80100cd300000000000000de80100ce3000000000000017e80100cf3000000000000021e80100d0300000000000002be80100d13000000000000035e80100d2300000000000003fe80100d33000000000000049e80100d43000000000000053e80100d5300000000000005de80100d63000000000000067e80100d73000000000000071e80100d8300000000000007be80100d93000000000000085e80100da300000000000008fe80100db3000000000000099e80100dc30000000000000a3e80100dd30000000000000ade80100de30000000000000b7e80100df30000000000000c1e80100e030000000000000cbe80100e130000000000000d5e80100e230000000000000dfe80100e330000000000000e9e80100e430000000000000f3e80100e530000000000000fde80100e63000000000000007e90100e73000000000000011e90100e8300000000000001be90100e93000000000000025e90100ea300000000000002fe90100eb3000000000000039e90100ec3000000000000043e90100ed300000000000004de90100ee3000000000000057e90100ef3000000000000061e90100f0300000000000006be90100f13000000000000075e90100f2300000000000007fe90100f33000000000000089e90100f43000000000000093e90100f5300000000000009de90100f630000000000000a7e90100f730000000000000b1e90100f830000000000000bbe90100f930000000000000c5e90100fa30000000000000cfe90100fb30000000000000d9e90100fc30000000000000e3e90100fd30000000000000ede90100fe30000000000000f7e90100ff3000000000000001ea010000310000000000000bea0100013100000000000015ea010002310000000000001fea0100033100000000000029ea0100043100000000000033ea010005310000000000003dea0100063100000000000047ea0100073100000000000051ea010008310000000000005bea0100093100000000000065ea01000a310000000000006fea01000b3100000000000079ea01000c3100000000000083ea01000d310000000000008dea01000e3100000000000097ea01000f31000000000000a1ea01001031000000000000abea01001131000000000000b5ea01001231000000000000bfea01001331000000000000c9ea01001431000000000000d3ea01001531000000000000ddea01001631000000000000e7ea01001731000000000000f1ea01001831000000000000fbea0100193100000000000005eb01001a310000000000000feb01001b3100000000000019eb01001c3100000000000023eb01001d310000000000002deb01001e3100000000000037eb01001f3100000000000041eb010020310000000000004beb0100213100000000000055eb010022310000000000005feb0100233100000000000069eb0100243100000000000073eb010025310000000000007deb0100263100000000000087eb0100273100000000000091eb010028310000000000009beb01002931000000000000a5eb01002a31000000000000afeb01002b31000000000000b9eb01002c31000000000000c3eb01002d31000000000000cdeb01002e31000000000000d7eb01002f31000000000000e1eb01003031000000000000ebeb01003131000000000000f5eb01003231000000000000ffeb0100333100000000000009ec0100343100000000000013ec010035310000000000001dec0100363100000000000027ec0100373100000000000031ec010038310000000000003bec0100393100000000000045ec01003a310000000000004fec01003b3100000000000059ec01003c3100000000000063ec01003d310000000000006dec01003e3100000000000077ec01003f3100000000000081ec010040310000000000008bec0100413100000000000095ec010042310000000000009fec01004331000000000000a9ec01004431000000000000b3ec01004531000000000000bdec01004631000000000000c7ec01004731000000000000d1ec01004831000000000000dbec01004931000000000000e5ec01004a31000000000000efec01004b31000000000000f9ec01004c3100000000000003ed01004d310000000000000ded01004e3100000000000017ed01004f3100000000000021ed010050310000000000002bed0100513100000000000035ed010052310000000000003fed0100533100000000000049ed0100543100000000000053ed010055310000000000005ded0100563100000000000067ed0100573100000000000071ed010058310000000000007bed0100593100000000000085ed01005a310000000000008fed01005b3100000000000099ed01005c31000000000000a3ed01005d31000000000000aded01005e31000000000000b7ed01005f31000000000000c1ed01006031000000000000cbed01006131000000000000d5ed01006231000000000000dfed01006331000000000000e9ed01006431000000000000f3ed01006531000000000000fded0100663100000000000007ee0100673100000000000011ee010068310000000000001bee0100693100000000000025ee01006a310000000000002fee01006b3100000000000039ee01006c3100000000000043ee01006d310000000000004dee01006e3100000000000057ee01006f3100000000000061ee010070310000000000006bee0100713100000000000075ee010072310000000000007fee0100733100000000000089ee0100743100000000000093ee010075310000000000009dee01007631000000000000a7ee01007731000000000000b1ee01007831000000000000bbee01007931000000000000c5ee01007a31000000000000cfee01007b31000000000000d9ee01007c31000000000000e3ee01007d31000000000000edee01007e31000000000000f7ee01007f3100000000000001ef010080310000000000000bef0100813100000000000015ef010082310000000000001fef0100833100000000000029ef0100843100000000000033ef010085310000000000003def0100863100000000000047ef0100873100000000000051ef010088310000000000005bef0100893100000000000065ef01008a310000000000006fef01008b3100000000000079ef01008c3100000000000083ef01008d310000000000008def01008e3100000000000097ef01008f31000000000000a1ef01009031000000000000abef01009131000000000000b5ef01009231000000000000bfef01009331000000000000c9ef01009431000000000000d3ef01009531000000000000ddef01009631000000000000e7ef01009731000000000000f1ef01009831000000000000fbef0100993100000000000005f001009a310000000000000ff001009b3100000000000019f001009c3100000000000023f001009d310000000000002df001009e3100000000000037f001009f3100000000000041f00100a0310000000000004bf00100a13100000000000055f00100a2310000000000005ff00100a33100000000000069f00100a43100000000000073f00100a5310000000000007df00100a63100000000000087f00100a73100000000000091f00100a8310000000000009bf00100a931000000000000a5f00100aa31000000000000aff00100ab31000000000000b9f00100ac31000000000000c3f00100ad31000000000000cdf00100ae31000000000000d7f00100af31000000000000e1f00100b031000000000000ebf00100b131000000000000f5f00100b231000000000000fff00100b33100000000000009f10100b43100000000000013f10100b5310000000000001df10100b63100000000000027f10100b73100000000000031f10100b8310000000000003bf10100b93100000000000045f10100ba310000000000004ff10100bb3100000000000059f10100bc3100000000000063f10100bd310000000000006df10100be3100000000000077f10100bf3100000000000081f10100c0310000000000008bf10100c13100000000000095f10100c2310000000000009ff10100c331000000000000a9f10100c431000000000000b3f10100c531000000000000bdf10100c631000000000000c7f10100c731000000000000d1f10100c831000000000000dbf10100c931000000000000e5f10100ca31000000000000eff10100cb31000000000000f9f10100cc3100000000000003f20100cd310000000000000df20100ce3100000000000017f20100cf3100000000000021f20100d0310000000000002bf20100d13100000000000035f20100d2310000000000003ff20100d33100000000000049f20100d43100000000000053f20100d5310000000000005df20100d63100000000000067f20100d73100000000000071f20100d8310000000000007bf20100d93100000000000085f20100da310000000000008ff20100db3100000000000099f20100dc31000000000000a3f20100dd31000000000000adf20100de31000000000000b7f20100df31000000000000c1f20100e031000000000000cbf20100e131000000000000d5f20100e231000000000000dff20100e331000000000000e9f20100e431000000000000f3f20100e531000000000000fdf20100e63100000000000007f30100e73100000000000011f30100e8310000000000001bf30100e93100000000000025f30100ea310000000000002ff30100eb3100000000000039f30100ec3100000000000043f30100ed310000000000004df30100ee3100000000000057f30100ef3100000000000061f30100f0310000000000006bf30100f13100000000000075f30100f2310000000000007ff30100f33100000000000089f30100f43100000000000093f30100f5310000000000009df30100f631000000000000a7f30100f731000000000000b1f30100f831000000000000bbf30100f931000000000000c5f30100fa31000000000000cff30100fb31000000000000d9f30100fc31000000000000e3f30100fd31000000000000edf30100fe31000000000000f7f30100ff3100000000000001f4010000320000000000000bf40100013200000000000015f4010002320000000000001ff40100033200000000000029f40100043200000000000033f4010005320000000000003df40100063200000000000047f40100073200000000000051f4010008320000000000005bf40100093200000000000065f401000a320000000000006ff401000b3200000000000079f401000c3200000000000083f401000d320000000000008df401000e3200000000000097f401000f32000000000000a1f401001032000000000000abf401001132000000000000b5f401001232000000000000bff401001332000000000000c9f401001432000000000000d3f401001532000000000000ddf401001632000000000000e7f401001732000000000000f1f401001832000000000000fbf40100193200000000000005f501001a320000000000000ff501001b3200000000000019f501001c3200000000000023f501001d320000000000002df501001e3200000000000037f501001f3200000000000041f5010020320000000000004bf50100213200000000000055f5010022320000000000005ff50100233200000000000069f50100243200000000000073f5010025320000000000007df50100263200000000000087f50100273200000000000091f5010028320000000000009bf501002932000000000000a5f501002a32000000000000aff501002b32000000000000b9f501002c32000000000000c3f501002d32000000000000cdf501002e32000000000000d7f501002f32000000000000e1f501003032000000000000ebf501003132000000000000f5f501003232000000000000fff50100333200000000000009f60100343200000000000013f6010035320000000000001df60100363200000000000027f60100373200000000000031f6010038320000000000003bf60100393200000000000045f601003a320000000000004ff601003b3200000000000059f601003c3200000000000063f601003d320000000000006df601003e3200000000000077f601003f3200000000000081f6010040320000000000008bf60100413200000000000095f6010042320000000000009ff601004332000000000000a9f601004432000000000000b3f601004532000000000000bdf601004632000000000000c7f601004732000000000000d1f601004832000000000000dbf601004932000000000000e5f601004a32000000000000eff601004b32000000000000f9f601004c3200000000000003f701004d320000000000000df701004e3200000000000017f701004f3200000000000021f7010050320000000000002bf70100513200000000000035f7010052320000000000003ff70100533200000000000049f70100543200000000000053f7010055320000000000005df70100563200000000000067f70100573200000000000071f7010058320000000000007bf70100593200000000000085f701005a320000000000008ff701005b3200000000000099f701005c32000000000000a3f701005d32000000000000adf701005e32000000000000b7f701005f32000000000000c1f701006032000000000000cbf701006132000000000000d5f701006232000000000000dff701006332000000000000e9f701006432000000000000f3f701006532000000000000fdf70100663200000000000007f80100673200000000000011f8010068320000000000001bf80100693200000000000025f801006a320000000000002ff801006b3200000000000039f801006c3200000000000043f801006d320000000000004df801006e3200000000000057f801006f3200000000000061f8010070320000000000006bf80100713200000000000075f8010072320000000000007ff80100733200000000000089f80100743200000000000093f8010075320000000000009df801007632000000000000a7f801007732000000000000b1f801007832000000000000bbf801007932000000000000c5f801007a32000000000000cff801007b32000000000000d9f801007c32000000000000e3f801007d32000000000000edf801007e32000000000000f7f801007f3200000000000001f9010080320000000000000bf90100813200000000000015f9010082320000000000001ff90100833200000000000029f90100843200000000000033f9010085320000000000003df90100863200000000000047f90100873200000000000051f9010088320000000000005bf90100893200000000000065f901008a320000000000006ff901008b3200000000000079f901008c3200000000000083f901008d320000000000008df901008e3200000000000097f901008f32000000000000a1f901009032000000000000abf901009132000000000000b5f901009232000000000000bff901009332000000000000c9f901009432000000000000d3f901009532000000000000ddf901009632000000000000e7f901009732000000000000f1f901009832000000000000fbf90100993200000000000005fa01009a320000000000000ffa01009b3200000000000019fa01009c3200000000000023fa01009d320000000000002dfa01009e3200000000000037fa01009f3200000000000041fa0100a0320000000000004bfa0100a13200000000000055fa0100a2320000000000005ffa0100a33200000000000069fa0100a43200000000000073fa0100a5320000000000007dfa0100a63200000000000087fa0100a73200000000000091fa0100a8320000000000009bfa0100a932000000000000a5fa0100aa32000000000000affa0100ab32000000000000b9fa0100ac32000000000000c3fa0100ad32000000000000cdfa0100ae32000000000000d7fa0100af32000000000000e1fa0100b032000000000000ebfa0100b132000000000000f5fa0100b232000000000000fffa0100b33200000000000009fb0100b43200000000000013fb0100b5320000000000001dfb0100b63200000000000027fb0100b73200000000000031fb0100b8320000000000003bfb0100b93200000000000045fb0100ba320000000000004ffb0100bb3200000000000059fb0100bc3200000000000063fb0100bd320000000000006dfb0100be3200000000000077fb0100bf3200000000000081fb0100c0320000000000008bfb0100c13200000000000095fb0100c2320000000000009ffb0100c332000000000000a9fb0100c432000000000000b3fb0100c532000000000000bdfb0100c632000000000000c7fb0100c732000000000000d1fb0100c832000000000000dbfb0100c932000000000000e5fb0100ca32000000000000effb0100cb32000000000000f9fb0100cc3200000000000003fc0100cd320000000000000dfc0100ce3200000000000017fc0100cf3200000000000021fc0100d0320000000000002bfc0100d13200000000000035fc0100d2320000000000003ffc0100d33200000000000049fc0100d43200000000000053fc0100d5320000000000005dfc0100d63200000000000067fc0100d73200000000000071fc0100d8320000000000007bfc0100d93200000000000085fc0100da320000000000008ffc0100db3200000000000099fc0100dc32000000000000a3fc0100dd32000000000000adfc0100de32000000000000b7fc0100df32000000000000c1fc0100e032000000000000cbfc0100e132000000000000d5fc0100e232000000000000dffc0100e332000000000000e9fc0100e432000000000000f3fc0100e532000000000000fdfc0100e63200000000000007fd0100e73200000000000011fd0100e8320000000000001bfd0100e93200000000000025fd0100ea320000000000002ffd0100eb3200000000000039fd0100ec3200000000000043fd0100ed320000000000004dfd0100ee3200000000000057fd0100ef3200000000000061fd0100f0320000000000006bfd0100f13200000000000075fd0100f2320000000000007ffd0100f33200000000000089fd0100f43200000000000093fd0100f5320000000000009dfd0100f632000000000000a7fd0100f732000000000000b1fd0100f832000000000000bbfd0100f932000000000000c5fd0100fa32000000000000cffd0100fb32000000000000d9fd0100fc32000000000000e3fd0100fd32000000000000edfd0100fe32000000000000f7fd0100ff3200000000000001fe010000330000000000000bfe0100013300000000000015fe010002330000000000001ffe0100033300000000000029fe0100043300000000000033fe010005330000000000003dfe0100063300000000000047fe0100073300000000000051fe010008330000000000005bfe0100093300000000000065fe01000a330000000000006ffe01000b3300000000000079fe01000c3300000000000083fe01000d330000000000008dfe01000e3300000000000097fe01000f33000000000000a1fe01001033000000000000abfe01001133000000000000b5fe01001233000000000000bffe01001333000000000000c9fe01001433000000000000d3fe01001533000000000000ddfe01001633000000000000e7fe01001733000000000000f1fe01001833000000000000fbfe0100193300000000000005ff01001a330000000000000fff01001b3300000000000019ff01001c3300000000000023ff01001d330000000000002dff01001e3300000000000037ff01001f3300000000000041ff010020330000000000004bff0100213300000000000055ff010022330000000000005fff0100233300000000000069ff0100243300000000000073ff010025330000000000007dff0100263300000000000087ff0100273300000000000091ff010028330000000000009bff01002933000000000000a5ff01002a33000000000000afff01002b33000000000000b9ff01002c33000000000000c3ff01002d33000000000000cdff01002e33000000000000d7ff01002f33000000000000e1ff01003033000000000000ebff01003133000000000000f5ff01003233000000000000ffff010033330000000000000900020034330000000000001300020035330000000000001d00020036330000000000002700020037330000000000003100020038330000000000003b0002003933000000000000450002003a330000000000004f0002003b33000000000000590002003c33000000000000630002003d330000000000006d0002003e33000000000000770002003f330000000000008100020040330000000000008b00020041330000000000009500020042330000000000009f0002004333000000000000a90002004433000000000000b30002004533000000000000bd0002004633000000000000c70002004733000000000000d10002004833000000000000db0002004933000000000000e50002004a33000000000000ef0002004b33000000000000f90002004c33000000000000030102004d330000000000000d0102004e33000000000000170102004f330000000000002101020050330000000000002b01020051330000000000003501020052330000000000003f01020053330000000000004901020054330000000000005301020055330000000000005d01020056330000000000006701020057330000000000007101020058330000000000007b0102005933000000000000850102005a330000000000008f0102005b33000000000000990102005c33000000000000a30102005d33000000000000ad0102005e33000000000000b70102005f33000000000000c10102006033000000000000cb0102006133000000000000d50102006233000000000000df0102006333000000000000e90102006433000000000000f30102006533000000000000fd01020066330000000000000702020067330000000000001102020068330000000000001b0202006933000000000000250202006a330000000000002f0202006b33000000000000390202006c33000000000000430202006d330000000000004d0202006e33000000000000570202006f330000000000006102020070330000000000006b02020071330000000000007502020072330000000000007f02020073330000000000008902020074330000000000009302020075330000000000009d0202007633000000000000a70202007733000000000000b10202007833000000000000bb0202007933000000000000c50202007a33000000000000cf0202007b33000000000000d90202007c33000000000000e30202007d33000000000000ed0202007e33000000000000f70202007f330000000000000103020080330000000000000b03020081330000000000001503020082330000000000001f03020083330000000000002903020084330000000000003303020085330000000000003d03020086330000000000004703020087330000000000005103020088330000000000005b0302008933000000000000650302008a330000000000006f0302008b33000000000000790302008c33000000000000830302008d330000000000008d0302008e33000000000000970302008f33000000000000a10302009033000000000000ab0302009133000000000000b50302009233000000000000bf0302009333000000000000c90302009433000000000000d30302009533000000000000dd0302009633000000000000e70302009733000000000000f10302009833000000000000fb0302009933000000000000050402009a330000000000000f0402009b33000000000000190402009c33000000000000230402009d330000000000002d0402009e33000000000000370402009f3300000000000041040200a0330000000000004b040200a13300000000000055040200a2330000000000005f040200a33300000000000069040200a43300000000000073040200a5330000000000007d040200a63300000000000087040200a73300000000000091040200a8330000000000009b040200a933000000000000a5040200aa33000000000000af040200ab33000000000000b9040200ac33000000000000c3040200ad33000000000000cd040200ae33000000000000d7040200af33000000000000e1040200b033000000000000eb040200b133000000000000f5040200b233000000000000ff040200b33300000000000009050200b43300000000000013050200b5330000000000001d050200b63300000000000027050200b73300000000000031050200b8330000000000003b050200b93300000000000045050200ba330000000000004f050200bb3300000000000059050200bc3300000000000063050200bd330000000000006d050200be3300000000000077050200bf3300000000000081050200c0330000000000008b050200c13300000000000095050200c2330000000000009f050200c333000000000000a9050200c433000000000000b3050200c533000000000000bd050200c633000000000000c7050200c733000000000000d1050200c833000000000000db050200c933000000000000e5050200ca33000000000000ef050200cb33000000000000f9050200cc3300000000000003060200cd330000000000000d060200ce3300000000000017060200cf3300000000000021060200d0330000000000002b060200d13300000000000035060200d2330000000000003f060200d33300000000000049060200d43300000000000053060200d5330000000000005d060200d63300000000000067060200d73300000000000071060200d8330000000000007b060200d93300000000000085060200da330000000000008f060200db3300000000000099060200dc33000000000000a3060200dd33000000000000ad060200de33000000000000b7060200df33000000000000c1060200e033000000000000cb060200e133000000000000d5060200e233000000000000df060200e333000000000000e9060200e433000000000000f3060200e533000000000000fd060200e63300000000000007070200e73300000000000011070200e8330000000000001b070200e93300000000000025070200ea330000000000002f070200eb3300000000000039070200ec3300000000000043070200ed330000000000004d070200ee3300000000000057070200ef3300000000000061070200f0330000000000006b070200f13300000000000075070200f2330000000000007f070200f33300000000000089070200f43300000000000093070200f5330000000000009d070200f633000000000000a7070200f733000000000000b1070200f833000000000000bb070200f933000000000000c5070200fa33000000000000cf070200fb33000000000000d9070200fc33000000000000e3070200fd33000000000000ed070200fe33000000000000f7070200ff330000000000000108020000340000000000000b08020001340000000000001508020002340000000000001f08020003340000000000002908020004340000000000003308020005340000000000003d08020006340000000000004708020007340000000000005108020008340000000000005b0802000934000000000000650802000a340000000000006f0802000b34000000000000790802000c34000000000000830802000d340000000000008d0802000e34000000000000970802000f34000000000000a10802001034000000000000ab0802001134000000000000b50802001234000000000000bf0802001334000000000000c90802001434000000000000d30802001534000000000000dd0802001634000000000000e70802001734000000000000f10802001834000000000000fb0802001934000000000000050902001a340000000000000f0902001b34000000000000190902001c34000000000000230902001d340000000000002d0902001e34000000000000370902001f340000000000004109020020340000000000004b09020021340000000000005509020022340000000000005f09020023340000000000006909020024340000000000007309020025340000000000007d09020026340000000000008709020027340000000000009109020028340000000000009b0902002934000000000000a50902002a34000000000000af0902002b34000000000000b90902002c34000000000000c30902002d34000000000000cd0902002e34000000000000d70902002f34000000000000e10902003034000000000000eb0902003134000000000000f50902003234000000000000ff0902003334000000000000090a02003434000000000000130a020035340000000000001d0a02003634000000000000270a02003734000000000000310a020038340000000000003b0a02003934000000000000450a02003a340000000000004f0a02003b34000000000000590a02003c34000000000000630a02003d340000000000006d0a02003e34000000000000770a02003f34000000000000810a020040340000000000008b0a02004134000000000000950a020042340000000000009f0a02004334000000000000a90a02004434000000000000b30a02004534000000000000bd0a02004634000000000000c70a02004734000000000000d10a02004834000000000000db0a02004934000000000000e50a02004a34000000000000ef0a02004b34000000000000f90a02004c34000000000000030b02004d340000000000000d0b02004e34000000000000170b02004f34000000000000210b020050340000000000002b0b02005134000000000000350b020052340000000000003f0b02005334000000000000490b02005434000000000000530b020055340000000000005d0b02005634000000000000670b02005734000000000000710b020058340000000000007b0b02005934000000000000850b02005a340000000000008f0b02005b34000000000000990b02005c34000000000000a30b02005d34000000000000ad0b02005e34000000000000b70b02005f34000000000000c10b02006034000000000000cb0b02006134000000000000d50b02006234000000000000df0b02006334000000000000e90b02006434000000000000f30b02006534000000000000fd0b02006634000000000000070c02006734000000000000110c020068340000000000001b0c02006934000000000000250c02006a340000000000002f0c02006b34000000000000390c02006c34000000000000430c02006d340000000000004d0c02006e34000000000000570c02006f34000000000000610c020070340000000000006b0c02007134000000000000750c020072340000000000007f0c02007334000000000000890c02007434000000000000930c020075340000000000009d0c02007634000000000000a70c02007734000000000000b10c02007834000000000000bb0c02007934000000000000c50c02007a34000000000000cf0c02007b34000000000000d90c02007c34000000000000e30c02007d34000000000000ed0c02007e34000000000000f70c02007f34000000000000010d020080340000000000000b0d02008134000000000000150d020082340000000000001f0d02008334000000000000290d02008434000000000000330d020085340000000000003d0d02008634000000000000470d02008734000000000000510d020088340000000000005b0d02008934000000000000650d02008a340000000000006f0d02008b34000000000000790d02008c34000000000000830d02008d340000000000008d0d02008e34000000000000970d02008f34000000000000a10d02009034000000000000ab0d02009134000000000000b50d02009234000000000000bf0d02009334000000000000c90d02009434000000000000d30d02009534000000000000dd0d02009634000000000000e70d02009734000000000000f10d02009834000000000000fb0d02009934000000000000050e02009a340000000000000f0e02009b34000000000000190e02009c34000000000000230e02009d340000000000002d0e02009e34000000000000370e02009f34000000000000410e0200a0340000000000004b0e0200a134000000000000550e0200a2340000000000005f0e0200a334000000000000690e0200a434000000000000730e0200a5340000000000007d0e0200a634000000000000870e0200a734000000000000910e0200a8340000000000009b0e0200a934000000000000a50e0200aa34000000000000af0e0200ab34000000000000b90e0200ac34000000000000c30e0200ad34000000000000cd0e0200ae34000000000000d70e0200af34000000000000e10e0200b034000000000000eb0e0200b134000000000000f50e0200b234000000000000ff0e0200b334000000000000090f0200b434000000000000130f0200b5340000000000001d0f0200b634000000000000270f0200b734000000000000310f0200b8340000000000003b0f0200b934000000000000450f0200ba340000000000004f0f0200bb34000000000000590f0200bc34000000000000630f0200bd340000000000006d0f0200be34000000000000770f0200bf34000000000000810f0200c0340000000000008b0f0200c134000000000000950f0200c2340000000000009f0f0200c334000000000000a90f0200c434000000000000b30f0200c534000000000000bd0f0200c634000000000000c70f0200c734000000000000d10f0200c834000000000000db0f0200c934000000000000e50f0200ca34000000000000ef0f0200cb34000000000000f90f0200cc3400000000000003100200cd340000000000000d100200ce3400000000000017100200cf3400000000000021100200d0340000000000002b100200d13400000000000035100200d2340000000000003f100200d33400000000000049100200d43400000000000053100200d5340000000000005d100200d63400000000000067100200d73400000000000071100200d8340000000000007b100200d93400000000000085100200da340000000000008f100200db3400000000000099100200dc34000000000000a3100200dd34000000000000ad100200de34000000000000b7100200df34000000000000c1100200e034000000000000cb100200e134000000000000d5100200e234000000000000df100200e334000000000000e9100200e434000000000000f3100200e534000000000000fd100200e63400000000000007110200e73400000000000011110200e8340000000000001b110200e93400000000000025110200ea340000000000002f110200eb3400000000000039110200ec3400000000000043110200ed340000000000004d110200ee3400000000000057110200ef3400000000000061110200f0340000000000006b110200f13400000000000075110200f2340000000000007f110200f33400000000000089110200f43400000000000093110200f5340000000000009d110200f634000000000000a7110200f734000000000000b1110200f834000000000000bb110200f934000000000000c5110200fa34000000000000cf110200fb34000000000000d9110200fc34000000000000e3110200fd34000000000000ed110200fe34000000000000f7110200ff340000000000000112020000350000000000000b12020001350000000000001512020002350000000000001f12020003350000000000002912020004350000000000003312020005350000000000003d12020006350000000000004712020007350000000000005112020008350000000000005b1202000935000000000000651202000a350000000000006f1202000b35000000000000791202000c35000000000000831202000d350000000000008d1202000e35000000000000971202000f35000000000000a11202001035000000000000ab1202001135000000000000b51202001235000000000000bf1202001335000000000000c91202001435000000000000d31202001535000000000000dd1202001635000000000000e71202001735000000000000f11202001835000000000000fb1202001935000000000000051302001a350000000000000f1302001b35000000000000191302001c35000000000000231302001d350000000000002d1302001e35000000000000371302001f350000000000004113020020350000000000004b13020021350000000000005513020022350000000000005f13020023350000000000006913020024350000000000007313020025350000000000007d13020026350000000000008713020027350000000000009113020028350000000000009b1302002935000000000000a51302002a35000000000000af1302002b35000000000000b91302002c35000000000000c31302002d35000000000000cd1302002e35000000000000d71302002f35000000000000e11302003035000000000000eb1302003135000000000000f51302003235000000000000ff13020033350000000000000914020034350000000000001314020035350000000000001d14020036350000000000002714020037350000000000003114020038350000000000003b1402003935000000000000451402003a350000000000004f1402003b35000000000000591402003c35000000000000631402003d350000000000006d1402003e35000000000000771402003f350000000000008114020040350000000000008b14020041350000000000009514020042350000000000009f1402004335000000000000a91402004435000000000000b31402004535000000000000bd1402004635000000000000c71402004735000000000000d11402004835000000000000db1402004935000000000000e51402004a35000000000000ef1402004b35000000000000f91402004c35000000000000031502004d350000000000000d1502004e35000000000000171502004f350000000000002115020050350000000000002b15020051350000000000003515020052350000000000003f15020053350000000000004915020054350000000000005315020055350000000000005d15020056350000000000006715020057350000000000007115020058350000000000007b1502005935000000000000851502005a350000000000008f1502005b35000000000000991502005c35000000000000a31502005d35000000000000ad1502005e35000000000000b71502005f35000000000000c11502006035000000000000cb1502006135000000000000d51502006235000000000000df1502006335000000000000e91502006435000000000000f31502006535000000000000fd15020066350000000000000716020067350000000000001116020068350000000000001b1602006935000000000000251602006a350000000000002f1602006b35000000000000391602006c35000000000000431602006d350000000000004d1602006e35000000000000571602006f350000000000006116020070350000000000006b16020071350000000000007516020072350000000000007f16020073350000000000008916020074350000000000009316020075350000000000009d1602007635000000000000a71602007735000000000000b11602007835000000000000bb1602007935000000000000c51602007a35000000000000cf1602007b35000000000000d91602007c35000000000000e31602007d35000000000000ed1602007e35000000000000f71602007f350000000000000117020080350000000000000b17020081350000000000001517020082350000000000001f17020083350000000000002917020084350000000000003317020085350000000000003d17020086350000000000004717020087350000000000005117020088350000000000005b1702008935000000000000651702008a350000000000006f1702008b35000000000000791702008c35000000000000831702008d350000000000008d1702008e35000000000000971702008f35000000000000a11702009035000000000000ab1702009135000000000000b51702009235000000000000bf1702009335000000000000c91702009435000000000000d31702009535000000000000dd1702009635000000000000e71702009735000000000000f11702009835000000000000fb1702009935000000000000051802009a350000000000000f1802009b35000000000000191802009c35000000000000231802009d350000000000002d1802009e35000000000000371802009f3500000000000041180200a0350000000000004b180200a13500000000000055180200a2350000000000005f180200a33500000000000069180200a43500000000000073180200a5350000000000007d180200a63500000000000087180200a73500000000000091180200a8350000000000009b180200a935000000000000a5180200aa35000000000000af180200ab35000000000000b9180200ac35000000000000c3180200ad35000000000000cd180200ae35000000000000d7180200af35000000000000e1180200b035000000000000eb180200b135000000000000f5180200b235000000000000ff180200b33500000000000009190200b43500000000000013190200b5350000000000001d190200b63500000000000027190200b73500000000000031190200b8350000000000003b190200b93500000000000045190200ba350000000000004f190200bb3500000000000059190200bc3500000000000063190200bd350000000000006d190200be3500000000000077190200bf3500000000000081190200c0350000000000008b190200c13500000000000095190200c2350000000000009f190200c335000000000000a9190200c435000000000000b3190200c535000000000000bd190200c635000000000000c7190200c735000000000000d1190200c835000000000000db190200c935000000000000e5190200ca35000000000000ef190200cb35000000000000f9190200cc35000000000000031a0200cd350000000000000d1a0200ce35000000000000171a0200cf35000000000000211a0200d0350000000000002b1a0200d135000000000000351a0200d2350000000000003f1a0200d335000000000000491a0200d435000000000000531a0200d5350000000000005d1a0200d635000000000000671a0200d735000000000000711a0200d8350000000000007b1a0200d935000000000000851a0200da350000000000008f1a0200db35000000000000991a0200dc35000000000000a31a0200dd35000000000000ad1a0200de35000000000000b71a0200df35000000000000c11a0200e035000000000000cb1a0200e135000000000000d51a0200e235000000000000df1a0200e335000000000000e91a0200e435000000000000f31a0200e535000000000000fd1a0200e635000000000000071b0200e735000000000000111b0200e8350000000000001b1b0200e935000000000000251b0200ea350000000000002f1b0200eb35000000000000391b0200ec35000000000000431b0200ed350000000000004d1b0200ee35000000000000571b0200ef35000000000000611b0200f0350000000000006b1b0200f135000000000000751b0200f2350000000000007f1b0200f335000000000000891b0200f435000000000000931b0200f5350000000000009d1b0200f635000000000000a71b0200f735000000000000b11b0200f835000000000000bb1b0200f935000000000000c51b0200fa35000000000000cf1b0200fb35000000000000d91b0200fc35000000000000e31b0200fd35000000000000ed1b0200fe35000000000000f71b0200ff35000000000000011c020000360000000000000b1c02000136000000000000151c020002360000000000001f1c02000336000000000000291c02000436000000000000331c020005360000000000003d1c02000636000000000000471c02000736000000000000511c020008360000000000005b1c02000936000000000000651c02000a360000000000006f1c02000b36000000000000791c02000c36000000000000831c02000d360000000000008d1c02000e36000000000000971c02000f36000000000000a11c02001036000000000000ab1c02001136000000000000b51c02001236000000000000bf1c02001336000000000000c91c02001436000000000000d31c02001536000000000000dd1c02001636000000000000e71c02001736000000000000f11c02001836000000000000fb1c02001936000000000000051d02001a360000000000000f1d02001b36000000000000191d02001c36000000000000231d02001d360000000000002d1d02001e36000000000000371d02001f36000000000000411d020020360000000000004b1d02002136000000000000551d020022360000000000005f1d02002336000000000000691d02002436000000000000731d020025360000000000007d1d02002636000000000000871d02002736000000000000911d020028360000000000009b1d02002936000000000000a51d02002a36000000000000af1d02002b36000000000000b91d02002c36000000000000c31d02002d36000000000000cd1d02002e36000000000000d71d02002f36000000000000e11d02003036000000000000eb1d02003136000000000000f51d02003236000000000000ff1d02003336000000000000091e02003436000000000000131e020035360000000000001d1e02003636000000000000271e02003736000000000000311e020038360000000000003b1e02003936000000000000451e02003a360000000000004f1e02003b36000000000000591e02003c36000000000000631e02003d360000000000006d1e02003e36000000000000771e02003f36000000000000811e020040360000000000008b1e02004136000000000000951e020042360000000000009f1e02004336000000000000a91e02004436000000000000b31e02004536000000000000bd1e02004636000000000000c71e02004736000000000000d11e02004836000000000000db1e02004936000000000000e51e02004a36000000000000ef1e02004b36000000000000f91e02004c36000000000000031f02004d360000000000000d1f02004e36000000000000171f02004f36000000000000211f020050360000000000002b1f02005136000000000000351f020052360000000000003f1f02005336000000000000491f02005436000000000000531f020055360000000000005d1f02005636000000000000671f02005736000000000000711f020058360000000000007b1f02005936000000000000851f02005a360000000000008f1f02005b36000000000000991f02005c36000000000000a31f02005d36000000000000ad1f02005e36000000000000b71f02005f36000000000000c11f02006036000000000000cb1f02006136000000000000d51f02006236000000000000df1f02006336000000000000e91f02006436000000000000f31f02006536000000000000fd1f020066360000000000000720020067360000000000001120020068360000000000001b2002006936000000000000252002006a360000000000002f2002006b36000000000000392002006c36000000000000432002006d360000000000004d2002006e36000000000000572002006f360000000000006120020070360000000000006b20020071360000000000007520020072360000000000007f20020073360000000000008920020074360000000000009320020075360000000000009d2002007636000000000000a72002007736000000000000b12002007836000000000000bb2002007936000000000000c52002007a36000000000000cf2002007b36000000000000d92002007c36000000000000e32002007d36000000000000ed2002007e36000000000000f72002007f360000000000000121020080360000000000000b21020081360000000000001521020082360000000000001f21020083360000000000002921020084360000000000003321020085360000000000003d21020086360000000000004721020087360000000000005121020088360000000000005b2102008936000000000000652102008a360000000000006f2102008b36000000000000792102008c36000000000000832102008d360000000000008d2102008e36000000000000972102008f36000000000000a12102009036000000000000ab2102009136000000000000b52102009236000000000000bf2102009336000000000000c92102009436000000000000d32102009536000000000000dd2102009636000000000000e72102009736000000000000f12102009836000000000000fb2102009936000000000000052202009a360000000000000f2202009b36000000000000192202009c36000000000000232202009d360000000000002d2202009e36000000000000372202009f3600000000000041220200a0360000000000004b220200a13600000000000055220200a2360000000000005f220200a33600000000000069220200a43600000000000073220200a5360000000000007d220200a63600000000000087220200a73600000000000091220200a8360000000000009b220200a936000000000000a5220200aa36000000000000af220200ab36000000000000b9220200ac36000000000000c3220200ad36000000000000cd220200ae36000000000000d7220200af36000000000000e1220200b036000000000000eb220200b136000000000000f5220200b236000000000000ff220200b33600000000000009230200b43600000000000013230200b5360000000000001d230200b63600000000000027230200b73600000000000031230200b8360000000000003b230200b93600000000000045230200ba360000000000004f230200bb3600000000000059230200bc3600000000000063230200bd360000000000006d230200be3600000000000077230200bf3600000000000081230200c0360000000000008b230200c13600000000000095230200c2360000000000009f230200c336000000000000a9230200c436000000000000b3230200c536000000000000bd230200c636000000000000c7230200c736000000000000d1230200c836000000000000db230200c936000000000000e5230200ca36000000000000ef230200cb36000000000000f9230200cc3600000000000003240200cd360000000000000d240200ce3600000000000017240200cf3600000000000021240200d0360000000000002b240200d13600000000000035240200d2360000000000003f240200d33600000000000049240200d43600000000000053240200d5360000000000005d240200d63600000000000067240200d73600000000000071240200d8360000000000007b240200d93600000000000085240200da360000000000008f240200db3600000000000099240200dc36000000000000a3240200dd36000000000000ad240200de36000000000000b7240200df36000000000000c1240200e036000000000000cb240200e136000000000000d5240200e236000000000000df240200e336000000000000e9240200e436000000000000f3240200e536000000000000fd240200e63600000000000007250200e73600000000000011250200e8360000000000001b250200e93600000000000025250200ea360000000000002f250200eb3600000000000039250200ec3600000000000043250200ed360000000000004d250200ee3600000000000057250200ef3600000000000061250200f0360000000000006b250200f13600000000000075250200f2360000000000007f250200f33600000000000089250200f43600000000000093250200f5360000000000009d250200f636000000000000a7250200f736000000000000b1250200f836000000000000bb250200f936000000000000c5250200fa36000000000000cf250200fb36000000000000d9250200fc36000000000000e3250200fd36000000000000ed250200fe36000000000000f7250200ff360000000000000126020000370000000000000b26020001370000000000001526020002370000000000001f26020003370000000000002926020004370000000000003326020005370000000000003d26020006370000000000004726020007370000000000005126020008370000000000005b2602000937000000000000652602000a370000000000006f2602000b37000000000000792602000c37000000000000832602000d370000000000008d2602000e37000000000000972602000f37000000000000a12602001037000000000000ab2602001137000000000000b52602001237000000000000bf2602001337000000000000c92602001437000000000000d32602001537000000000000dd2602001637000000000000e72602001737000000000000f12602001837000000000000fb2602001937000000000000052702001a370000000000000f2702001b37000000000000192702001c37000000000000232702001d370000000000002d2702001e37000000000000372702001f370000000000004127020020370000000000004b27020021370000000000005527020022370000000000005f27020023370000000000006927020024370000000000007327020025370000000000007d27020026370000000000008727020027370000000000009127020028370000000000009b2702002937000000000000a52702002a37000000000000af2702002b37000000000000b92702002c37000000000000c32702002d37000000000000cd2702002e37000000000000d72702002f37000000000000e12702003037000000000000eb2702003137000000000000f52702003237000000000000ff27020033370000000000000928020034370000000000001328020035370000000000001d28020036370000000000002728020037370000000000003128020038370000000000003b2802003937000000000000452802003a370000000000004f2802003b37000000000000592802003c37000000000000632802003d370000000000006d2802003e37000000000000772802003f370000000000008128020040370000000000008b28020041370000000000009528020042370000000000009f2802004337000000000000a92802004437000000000000b32802004537000000000000bd2802004637000000000000c72802004737000000000000d12802004837000000000000db2802004937000000000000e52802004a37000000000000ef2802004b37000000000000f92802004c37000000000000032902004d370000000000000d2902004e37000000000000172902004f370000000000002129020050370000000000002b29020051370000000000003529020052370000000000003f29020053370000000000004929020054370000000000005329020055370000000000005d29020056370000000000006729020057370000000000007129020058370000000000007b2902005937000000000000852902005a370000000000008f2902005b37000000000000992902005c37000000000000a32902005d37000000000000ad2902005e37000000000000b72902005f37000000000000c12902006037000000000000cb2902006137000000000000d52902006237000000000000df2902006337000000000000e92902006437000000000000f32902006537000000000000fd2902006637000000000000072a02006737000000000000112a020068370000000000001b2a02006937000000000000252a02006a370000000000002f2a02006b37000000000000392a02006c37000000000000432a02006d370000000000004d2a02006e37000000000000572a02006f37000000000000612a020070370000000000006b2a02007137000000000000752a020072370000000000007f2a02007337000000000000892a02007437000000000000932a020075370000000000009d2a02007637000000000000a72a02007737000000000000b12a02007837000000000000bb2a02007937000000000000c52a02007a37000000000000cf2a02007b37000000000000d92a02007c37000000000000e32a02007d37000000000000ed2a02007e37000000000000f72a02007f37000000000000012b020080370000000000000b2b02008137000000000000152b020082370000000000001f2b02008337000000000000292b02008437000000000000332b020085370000000000003d2b02008637000000000000472b02008737000000000000512b020088370000000000005b2b02008937000000000000652b02008a370000000000006f2b02008b37000000000000792b02008c37000000000000832b02008d370000000000008d2b02008e37000000000000972b02008f37000000000000a12b02009037000000000000ab2b02009137000000000000b52b02009237000000000000bf2b02009337000000000000c92b02009437000000000000d32b02009537000000000000dd2b02009637000000000000e72b02009737000000000000f12b02009837000000000000fb2b02009937000000000000052c02009a370000000000000f2c02009b37000000000000192c02009c37000000000000232c02009d370000000000002d2c02009e37000000000000372c02009f37000000000000412c0200a0370000000000004b2c0200a137000000000000552c0200a2370000000000005f2c0200a337000000000000692c0200a437000000000000732c0200a5370000000000007d2c0200a637000000000000872c0200a737000000000000912c0200a8370000000000009b2c0200a937000000000000a52c0200aa37000000000000af2c0200ab37000000000000b92c0200ac37000000000000c32c0200ad37000000000000cd2c0200ae37000000000000d72c0200af37000000000000e12c0200b037000000000000eb2c0200b137000000000000f52c0200b237000000000000ff2c0200b337000000000000092d0200b437000000000000132d0200b5370000000000001d2d0200b637000000000000272d0200b737000000000000312d0200b8370000000000003b2d0200b937000000000000452d0200ba370000000000004f2d0200bb37000000000000592d0200bc37000000000000632d0200bd370000000000006d2d0200be37000000000000772d0200bf37000000000000812d0200c0370000000000008b2d0200c137000000000000952d0200c2370000000000009f2d0200c337000000000000a92d0200c437000000000000b32d0200c537000000000000bd2d0200c637000000000000c72d0200c737000000000000d12d0200c837000000000000db2d0200c937000000000000e52d0200ca37000000000000ef2d0200cb37000000000000f92d0200cc37000000000000032e0200cd370000000000000d2e0200ce37000000000000172e0200cf37000000000000212e0200d0370000000000002b2e0200d137000000000000352e0200d2370000000000003f2e0200d337000000000000492e0200d437000000000000532e0200d5370000000000005d2e0200d637000000000000672e0200d737000000000000712e0200d8370000000000007b2e0200d937000000000000852e0200da370000000000008f2e0200db37000000000000992e0200dc37000000000000a32e0200dd37000000000000ad2e0200de37000000000000b72e0200df37000000000000c12e0200e037000000000000cb2e0200e137000000000000d52e0200e237000000000000df2e0200e337000000000000e92e0200e437000000000000f32e0200e537000000000000fd2e0200e637000000000000072f0200e737000000000000112f0200e8370000000000001b2f0200e937000000000000252f0200ea370000000000002f2f0200eb37000000000000392f0200ec37000000000000432f0200ed370000000000004d2f0200ee37000000000000572f0200ef37000000000000612f0200f0370000000000006b2f0200f137000000000000752f0200f2370000000000007f2f0200f337000000000000892f0200f437000000000000932f0200f5370000000000009d2f0200f637000000000000a72f0200f737000000000000b12f0200f837000000000000bb2f0200f937000000000000c52f0200fa37000000000000cf2f0200fb37000000000000d92f0200fc37000000000000e32f0200fd37000000000000ed2f0200fe37000000000000f72f0200ff370000000000000130020000380000000000000b30020001380000000000001530020002380000000000001f30020003380000000000002930020004380000000000003330020005380000000000003d30020006380000000000004730020007380000000000005130020008380000000000005b3002000938000000000000653002000a380000000000006f3002000b38000000000000793002000c38000000000000833002000d380000000000008d3002000e38000000000000973002000f38000000000000a13002001038000000000000ab3002001138000000000000b53002001238000000000000bf3002001338000000000000c93002001438000000000000d33002001538000000000000dd3002001638000000000000e73002001738000000000000f13002001838000000000000fb3002001938000000000000053102001a380000000000000f3102001b38000000000000193102001c38000000000000233102001d380000000000002d3102001e38000000000000373102001f380000000000004131020020380000000000004b31020021380000000000005531020022380000000000005f31020023380000000000006931020024380000000000007331020025380000000000007d31020026380000000000008731020027380000000000009131020028380000000000009b3102002938000000000000a53102002a38000000000000af3102002b38000000000000b93102002c38000000000000c33102002d38000000000000cd3102002e38000000000000d73102002f38000000000000e13102003038000000000000eb3102003138000000000000f53102003238000000000000ff31020033380000000000000932020034380000000000001332020035380000000000001d32020036380000000000002732020037380000000000003132020038380000000000003b3202003938000000000000453202003a380000000000004f3202003b38000000000000593202003c38000000000000633202003d380000000000006d3202003e38000000000000773202003f380000000000008132020040380000000000008b32020041380000000000009532020042380000000000009f3202004338000000000000a93202004438000000000000b33202004538000000000000bd3202004638000000000000c73202004738000000000000d13202004838000000000000db3202004938000000000000e53202004a38000000000000ef3202004b38000000000000f93202004c38000000000000033302004d380000000000000d3302004e38000000000000173302004f380000000000002133020050380000000000002b33020051380000000000003533020052380000000000003f33020053380000000000004933020054380000000000005333020055380000000000005d33020056380000000000006733020057380000000000007133020058380000000000007b3302005938000000000000853302005a380000000000008f3302005b38000000000000993302005c38000000000000a33302005d38000000000000ad3302005e38000000000000b73302005f38000000000000c13302006038000000000000cb3302006138000000000000d53302006238000000000000df3302006338000000000000e93302006438000000000000f33302006538000000000000fd33020066380000000000000734020067380000000000001134020068380000000000001b3402006938000000000000253402006a380000000000002f3402006b38000000000000393402006c38000000000000433402006d380000000000004d3402006e38000000000000573402006f380000000000006134020070380000000000006b34020071380000000000007534020072380000000000007f34020073380000000000008934020074380000000000009334020075380000000000009d3402007638000000000000a73402007738000000000000b13402007838000000000000bb3402007938000000000000c53402007a38000000000000cf3402007b38000000000000d93402007c38000000000000e33402007d38000000000000ed3402007e38000000000000f73402007f380000000000000135020080380000000000000b35020081380000000000001535020082380000000000001f35020083380000000000002935020084380000000000003335020085380000000000003d35020086380000000000004735020087380000000000005135020088380000000000005b3502008938000000000000653502008a380000000000006f3502008b38000000000000793502008c38000000000000833502008d380000000000008d3502008e38000000000000973502008f38000000000000a13502009038000000000000ab3502009138000000000000b53502009238000000000000bf3502009338000000000000c93502009438000000000000d33502009538000000000000dd3502009638000000000000e73502009738000000000000f13502009838000000000000fb3502009938000000000000053602009a380000000000000f3602009b38000000000000193602009c38000000000000233602009d380000000000002d3602009e38000000000000373602009f3800000000000041360200a0380000000000004b360200a13800000000000055360200a2380000000000005f360200a33800000000000069360200a43800000000000073360200a5380000000000007d360200a63800000000000087360200a73800000000000091360200a8380000000000009b360200a938000000000000a5360200aa38000000000000af360200ab38000000000000b9360200ac38000000000000c3360200ad38000000000000cd360200ae38000000000000d7360200af38000000000000e1360200b038000000000000eb360200b138000000000000f5360200b238000000000000ff360200b33800000000000009370200b43800000000000013370200b5380000000000001d370200b63800000000000027370200b73800000000000031370200b8380000000000003b370200b93800000000000045370200ba380000000000004f370200bb3800000000000059370200bc3800000000000063370200bd380000000000006d370200be3800000000000077370200bf3800000000000081370200c0380000000000008b370200c13800000000000095370200c2380000000000009f370200c338000000000000a9370200c438000000000000b3370200c538000000000000bd370200c638000000000000c7370200c738000000000000d1370200c838000000000000db370200c938000000000000e5370200ca38000000000000ef370200cb38000000000000f9370200cc3800000000000003380200cd380000000000000d380200ce3800000000000017380200cf3800000000000021380200d0380000000000002b380200d13800000000000035380200d2380000000000003f380200d33800000000000049380200d43800000000000053380200d5380000000000005d380200d63800000000000067380200d73800000000000071380200d8380000000000007b380200d93800000000000085380200da380000000000008f380200db3800000000000099380200dc38000000000000a3380200dd38000000000000ad380200de38000000000000b7380200df38000000000000c1380200e038000000000000cb380200e138000000000000d5380200e238000000000000df380200e338000000000000e9380200e438000000000000f3380200e538000000000000fd380200e63800000000000007390200e73800000000000011390200e8380000000000001b390200e93800000000000025390200ea380000000000002f390200eb3800000000000039390200ec3800000000000043390200ed380000000000004d390200ee3800000000000057390200ef3800000000000061390200f0380000000000006b390200f13800000000000075390200f2380000000000007f390200f33800000000000089390200f43800000000000093390200f5380000000000009d390200f638000000000000a7390200f738000000000000b1390200f838000000000000bb390200f938000000000000c5390200fa38000000000000cf390200fb38000000000000d9390200fc38000000000000e3390200fd38000000000000ed390200fe38000000000000f7390200ff38000000000000013a020000390000000000000b3a02000139000000000000153a020002390000000000001f3a02000339000000000000293a02000439000000000000333a020005390000000000003d3a02000639000000000000473a02000739000000000000513a020008390000000000005b3a02000939000000000000653a02000a390000000000006f3a02000b39000000000000793a02000c39000000000000833a02000d390000000000008d3a02000e39000000000000973a02000f39000000000000a13a02001039000000000000ab3a02001139000000000000b53a02001239000000000000bf3a02001339000000000000c93a02001439000000000000d33a02001539000000000000dd3a02001639000000000000e73a02001739000000000000f13a02001839000000000000fb3a02001939000000000000053b02001a390000000000000f3b02001b39000000000000193b02001c39000000000000233b02001d390000000000002d3b02001e39000000000000373b02001f39000000000000413b020020390000000000004b3b02002139000000000000553b020022390000000000005f3b02002339000000000000693b02002439000000000000733b020025390000000000007d3b02002639000000000000873b02002739000000000000913b020028390000000000009b3b02002939000000000000a53b02002a39000000000000af3b02002b39000000000000b93b02002c39000000000000c33b02002d39000000000000cd3b02002e39000000000000d73b02002f39000000000000e13b02003039000000000000eb3b02003139000000000000f53b02003239000000000000ff3b02003339000000000000093c02003439000000000000133c020035390000000000001d3c02003639000000000000273c02003739000000000000313c020038390000000000003b3c02003939000000000000453c02003a390000000000004f3c02003b39000000000000593c02003c39000000000000633c02003d390000000000006d3c02003e39000000000000773c02003f39000000000000813c020040390000000000008b3c02004139000000000000953c020042390000000000009f3c02004339000000000000a93c02004439000000000000b33c02004539000000000000bd3c02004639000000000000c73c02004739000000000000d13c02004839000000000000db3c02004939000000000000e53c02004a39000000000000ef3c02004b39000000000000f93c02004c39000000000000033d02004d390000000000000d3d02004e39000000000000173d02004f39000000000000213d020050390000000000002b3d02005139000000000000353d020052390000000000003f3d02005339000000000000493d02005439000000000000533d020055390000000000005d3d02005639000000000000673d02005739000000000000713d020058390000000000007b3d02005939000000000000853d02005a390000000000008f3d02005b39000000000000993d02005c39000000000000a33d02005d39000000000000ad3d02005e39000000000000b73d02005f39000000000000c13d02006039000000000000cb3d02006139000000000000d53d02006239000000000000df3d02006339000000000000e93d02006439000000000000f33d02006539000000000000fd3d02006639000000000000073e02006739000000000000113e020068390000000000001b3e02006939000000000000253e02006a390000000000002f3e02006b39000000000000393e02006c39000000000000433e02006d390000000000004d3e02006e39000000000000573e02006f39000000000000613e020070390000000000006b3e02007139000000000000753e020072390000000000007f3e02007339000000000000893e02007439000000000000933e020075390000000000009d3e02007639000000000000a73e02007739000000000000b13e02007839000000000000bb3e02007939000000000000c53e02007a39000000000000cf3e02007b39000000000000d93e02007c39000000000000e33e02007d39000000000000ed3e02007e39000000000000f73e02007f39000000000000013f020080390000000000000b3f02008139000000000000153f020082390000000000001f3f02008339000000000000293f02008439000000000000333f020085390000000000003d3f02008639000000000000473f02008739000000000000513f020088390000000000005b3f02008939000000000000653f02008a390000000000006f3f02008b39000000000000793f02008c39000000000000833f02008d390000000000008d3f02008e39000000000000973f02008f39000000000000a13f02009039000000000000ab3f02009139000000000000b53f02009239000000000000bf3f02009339000000000000c93f02009439000000000000d33f02009539000000000000dd3f02009639000000000000e73f02009739000000000000f13f02009839000000000000fb3f02009939000000000000054002009a390000000000000f4002009b39000000000000194002009c39000000000000234002009d390000000000002d4002009e39000000000000374002009f3900000000000041400200a0390000000000004b400200a13900000000000055400200a2390000000000005f400200a33900000000000069400200a43900000000000073400200a5390000000000007d400200a63900000000000087400200a73900000000000091400200a8390000000000009b400200a939000000000000a5400200aa39000000000000af400200ab39000000000000b9400200ac39000000000000c3400200ad39000000000000cd400200ae39000000000000d7400200af39000000000000e1400200b039000000000000eb400200b139000000000000f5400200b239000000000000ff400200b33900000000000009410200b43900000000000013410200b5390000000000001d410200b63900000000000027410200b73900000000000031410200b8390000000000003b410200b93900000000000045410200ba390000000000004f410200bb3900000000000059410200bc3900000000000063410200bd390000000000006d410200be3900000000000077410200bf3900000000000081410200c0390000000000008b410200c13900000000000095410200c2390000000000009f410200c339000000000000a9410200c439000000000000b3410200c539000000000000bd410200c639000000000000c7410200c739000000000000d1410200c839000000000000db410200c939000000000000e5410200ca39000000000000ef410200cb39000000000000f9410200cc3900000000000003420200cd390000000000000d420200ce3900000000000017420200cf3900000000000021420200d0390000000000002b420200d13900000000000035420200d2390000000000003f420200d33900000000000049420200d43900000000000053420200d5390000000000005d420200d63900000000000067420200d73900000000000071420200d8390000000000007b420200d93900000000000085420200da390000000000008f420200db3900000000000099420200dc39000000000000a3420200dd39000000000000ad420200de39000000000000b7420200df39000000000000c1420200e039000000000000cb420200e139000000000000d5420200e239000000000000df420200e339000000000000e9420200e439000000000000f3420200e539000000000000fd420200e63900000000000007430200e73900000000000011430200e8390000000000001b430200e93900000000000025430200ea390000000000002f430200eb3900000000000039430200ec3900000000000043430200ed390000000000004d430200ee3900000000000057430200ef3900000000000061430200f0390000000000006b430200f13900000000000075430200f2390000000000007f430200f33900000000000089430200f43900000000000093430200f5390000000000009d430200f639000000000000a7430200f739000000000000b1430200f839000000000000bb430200f939000000000000c5430200fa39000000000000cf430200fb39000000000000d9430200fc39000000000000e3430200fd39000000000000ed430200fe39000000000000f7430200ff3900000000000001440200003a0000000000000b440200013a00000000000015440200023a0000000000001f440200033a00000000000029440200043a00000000000033440200053a0000000000003d440200063a00000000000047440200073a00000000000051440200083a0000000000005b440200093a000000000000654402000a3a0000000000006f4402000b3a000000000000794402000c3a000000000000834402000d3a0000000000008d4402000e3a000000000000974402000f3a000000000000a1440200103a000000000000ab440200113a000000000000b5440200123a000000000000bf440200133a000000000000c9440200143a000000000000d3440200153a000000000000dd440200163a000000000000e7440200173a000000000000f1440200183a000000000000fb440200193a000000000000054502001a3a0000000000000f4502001b3a000000000000194502001c3a000000000000234502001d3a0000000000002d4502001e3a000000000000374502001f3a00000000000041450200203a0000000000004b450200213a00000000000055450200223a0000000000005f450200233a00000000000069450200243a00000000000073450200253a0000000000007d450200263a00000000000087450200273a00000000000091450200283a0000000000009b450200293a000000000000a54502002a3a000000000000af4502002b3a000000000000b94502002c3a000000000000c34502002d3a000000000000cd4502002e3a000000000000d74502002f3a000000000000e1450200303a000000000000eb450200313a000000000000f5450200323a000000000000ff450200333a00000000000009460200343a00000000000013460200353a0000000000001d460200363a00000000000027460200373a00000000000031460200383a0000000000003b460200393a000000000000454602003a3a0000000000004f4602003b3a000000000000594602003c3a000000000000634602003d3a0000000000006d4602003e3a000000000000774602003f3a00000000000081460200403a0000000000008b460200413a00000000000095460200423a0000000000009f460200433a000000000000a9460200443a000000000000b3460200453a000000000000bd460200463a000000000000c7460200473a000000000000d1460200483a000000000000db460200493a000000000000e54602004a3a000000000000ef4602004b3a000000000000f94602004c3a000000000000034702004d3a0000000000000d4702004e3a000000000000174702004f3a00000000000021470200503a0000000000002b470200513a00000000000035470200523a0000000000003f470200533a00000000000049470200543a00000000000053470200553a0000000000005d470200563a00000000000067470200573a00000000000071470200583a0000000000007b470200593a000000000000854702005a3a0000000000008f4702005b3a000000000000994702005c3a000000000000a34702005d3a000000000000ad4702005e3a000000000000b74702005f3a000000000000c1470200603a000000000000cb470200613a000000000000d5470200623a000000000000df470200633a000000000000e9470200643a000000000000f3470200653a000000000000fd470200663a00000000000007480200673a00000000000011480200683a0000000000001b480200693a000000000000254802006a3a0000000000002f4802006b3a000000000000394802006c3a000000000000434802006d3a0000000000004d4802006e3a000000000000574802006f3a00000000000061480200703a0000000000006b480200713a00000000000075480200723a0000000000007f480200733a00000000000089480200743a00000000000093480200753a0000000000009d480200763a000000000000a7480200773a000000000000b1480200783a000000000000bb480200793a000000000000c54802007a3a000000000000cf4802007b3a000000000000d94802007c3a000000000000e34802007d3a000000000000ed4802007e3a000000000000f74802007f3a00000000000001490200803a0000000000000b490200813a00000000000015490200823a0000000000001f490200833a00000000000029490200843a00000000000033490200853a0000000000003d490200863a00000000000047490200873a00000000000051490200883a0000000000005b490200893a000000000000654902008a3a0000000000006f4902008b3a000000000000794902008c3a000000000000834902008d3a0000000000008d4902008e3a000000000000974902008f3a000000000000a1490200903a000000000000ab490200913a000000000000b5490200923a000000000000bf490200933a000000000000c9490200943a000000000000d3490200953a000000000000dd490200963a000000000000e7490200973a000000000000f1490200983a000000000000fb490200993a000000000000054a02009a3a0000000000000f4a02009b3a000000000000194a02009c3a000000000000234a02009d3a0000000000002d4a02009e3a000000000000374a02009f3a000000000000414a0200a03a0000000000004b4a0200a13a000000000000554a0200a23a0000000000005f4a0200a33a000000000000694a0200a43a000000000000734a0200a53a0000000000007d4a0200a63a000000000000874a0200a73a000000000000914a0200a83a0000000000009b4a0200a93a000000000000a54a0200aa3a000000000000af4a0200ab3a000000000000b94a0200ac3a000000000000c34a0200ad3a000000000000cd4a0200ae3a000000000000d74a0200af3a000000000000e14a0200b03a000000000000eb4a0200b13a000000000000f54a0200b23a000000000000ff4a0200b33a000000000000094b0200b43a000000000000134b0200b53a0000000000001d4b0200b63a000000000000274b0200b73a000000000000314b0200b83a0000000000003b4b0200b93a000000000000454b0200ba3a0000000000004f4b0200bb3a000000000000594b0200bc3a000000000000634b0200bd3a0000000000006d4b0200be3a000000000000774b0200bf3a000000000000814b0200c03a0000000000008b4b0200c13a000000000000954b0200c23a0000000000009f4b0200c33a000000000000a94b0200c43a000000000000b34b0200c53a000000000000bd4b0200c63a000000000000c74b0200c73a000000000000d14b0200c83a000000000000db4b0200c93a000000000000e54b0200ca3a000000000000ef4b0200cb3a000000000000f94b0200cc3a000000000000034c0200cd3a0000000000000d4c0200ce3a000000000000174c0200cf3a000000000000214c0200d03a0000000000002b4c0200d13a000000000000354c0200d23a0000000000003f4c0200d33a000000000000494c0200d43a000000000000534c0200d53a0000000000005d4c0200d63a000000000000674c0200d73a000000000000714c0200d83a0000000000007b4c0200d93a000000000000854c0200da3a0000000000008f4c0200db3a000000000000994c0200dc3a000000000000a34c0200dd3a000000000000ad4c0200de3a000000000000b74c0200df3a000000000000c14c0200e03a000000000000cb4c0200e13a000000000000d54c0200e23a000000000000df4c0200e33a000000000000e94c0200e43a000000000000f34c0200e53a000000000000fd4c0200e63a000000000000074d0200e73a000000000000114d0200e83a0000000000001b4d0200e93a000000000000254d0200ea3a0000000000002f4d0200eb3a000000000000394d0200ec3a000000000000434d0200ed3a0000000000004d4d0200ee3a000000000000574d0200ef3a000000000000614d0200f03a0000000000006b4d0200f13a000000000000754d0200f23a0000000000007f4d0200f33a000000000000894d0200f43a000000000000934d0200f53a0000000000009d4d0200f63a000000000000a74d0200f73a000000000000b14d0200f83a000000000000bb4d0200f93a000000000000c54d0200fa3a000000000000cf4d0200fb3a000000000000d94d0200fc3a000000000000e34d0200fd3a000000000000ed4d0200fe3a000000000000f74d0200ff3a000000000000014e0200003b0000000000000b4e0200013b000000000000154e0200023b0000000000001f4e0200033b000000000000294e0200043b000000000000334e0200053b0000000000003d4e0200063b000000000000474e0200073b000000000000514e0200083b0000000000005b4e0200093b000000000000654e02000a3b0000000000006f4e02000b3b000000000000794e02000c3b000000000000834e02000d3b0000000000008d4e02000e3b000000000000974e02000f3b000000000000a14e0200103b000000000000ab4e0200113b000000000000b54e0200123b000000000000bf4e0200133b000000000000c94e0200143b000000000000d34e0200153b000000000000dd4e0200163b000000000000e74e0200173b000000000000f14e0200183b000000000000fb4e0200193b000000000000054f02001a3b0000000000000f4f02001b3b000000000000194f02001c3b000000000000234f02001d3b0000000000002d4f02001e3b000000000000374f02001f3b000000000000414f0200203b0000000000004b4f0200213b000000000000554f0200223b0000000000005f4f0200233b000000000000694f0200243b000000000000734f0200253b0000000000007d4f0200263b000000000000874f0200273b000000000000914f0200283b0000000000009b4f0200293b000000000000a54f02002a3b000000000000af4f02002b3b000000000000b94f02002c3b000000000000c34f02002d3b000000000000cd4f02002e3b000000000000d74f02002f3b000000000000e14f0200303b000000000000eb4f0200313b000000000000f54f0200323b000000000000ff4f0200333b00000000000009500200343b00000000000013500200353b0000000000001d500200363b00000000000027500200373b00000000000031500200383b0000000000003b500200393b000000000000455002003a3b0000000000004f5002003b3b000000000000595002003c3b000000000000635002003d3b0000000000006d5002003e3b000000000000775002003f3b00000000000081500200403b0000000000008b500200413b00000000000095500200423b0000000000009f500200433b000000000000a9500200443b000000000000b3500200453b000000000000bd500200463b000000000000c7500200473b000000000000d1500200483b000000000000db500200493b000000000000e55002004a3b000000000000ef5002004b3b000000000000f95002004c3b000000000000035102004d3b0000000000000d5102004e3b000000000000175102004f3b00000000000021510200503b0000000000002b510200513b00000000000035510200523b0000000000003f510200533b00000000000049510200543b00000000000053510200553b0000000000005d510200563b00000000000067510200573b00000000000071510200583b0000000000007b510200593b000000000000855102005a3b0000000000008f5102005b3b000000000000995102005c3b000000000000a35102005d3b000000000000ad5102005e3b000000000000b75102005f3b000000000000c1510200603b000000000000cb510200613b000000000000d5510200623b000000000000df510200633b000000000000e9510200643b000000000000f3510200653b000000000000fd510200663b00000000000007520200673b00000000000011520200683b0000000000001b520200693b000000000000255202006a3b0000000000002f5202006b3b000000000000395202006c3b000000000000435202006d3b0000000000004d5202006e3b000000000000575202006f3b00000000000061520200703b0000000000006b520200713b00000000000075520200723b0000000000007f520200733b00000000000089520200743b00000000000093520200753b0000000000009d520200763b000000000000a7520200773b000000000000b1520200783b000000000000bb520200793b000000000000c55202007a3b000000000000cf5202007b3b000000000000d95202007c3b000000000000e35202007d3b000000000000ed5202007e3b000000000000f75202007f3b00000000000001530200803b0000000000000b530200813b00000000000015530200823b0000000000001f530200833b00000000000029530200843b00000000000033530200853b0000000000003d530200863b00000000000047530200873b00000000000051530200883b0000000000005b530200893b000000000000655302008a3b0000000000006f5302008b3b000000000000795302008c3b000000000000835302008d3b0000000000008d5302008e3b000000000000975302008f3b000000000000a1530200903b000000000000ab530200913b000000000000b5530200923b000000000000bf530200933b000000000000c9530200943b000000000000d3530200953b000000000000dd530200963b000000000000e7530200973b000000000000f1530200983b000000000000fb530200993b000000000000055402009a3b0000000000000f5402009b3b000000000000195402009c3b000000000000235402009d3b0000000000002d5402009e3b000000000000375402009f3b00000000000041540200a03b0000000000004b540200a13b00000000000055540200a23b0000000000005f540200a33b00000000000069540200a43b00000000000073540200a53b0000000000007d540200a63b00000000000087540200a73b00000000000091540200a83b0000000000009b540200a93b000000000000a5540200aa3b000000000000af540200ab3b000000000000b9540200ac3b000000000000c3540200ad3b000000000000cd540200ae3b000000000000d7540200af3b000000000000e1540200b03b000000000000eb540200b13b000000000000f5540200b23b000000000000ff540200b33b00000000000009550200b43b00000000000013550200b53b0000000000001d550200b63b00000000000027550200b73b00000000000031550200b83b0000000000003b550200b93b00000000000045550200ba3b0000000000004f550200bb3b00000000000059550200bc3b00000000000063550200bd3b0000000000006d550200be3b00000000000077550200bf3b00000000000081550200c03b0000000000008b550200c13b00000000000095550200c23b0000000000009f550200c33b000000000000a9550200c43b000000000000b3550200c53b000000000000bd550200c63b000000000000c7550200c73b000000000000d1550200c83b000000000000db550200c93b000000000000e5550200ca3b000000000000ef550200cb3b000000000000f9550200cc3b00000000000003560200cd3b0000000000000d560200ce3b00000000000017560200cf3b00000000000021560200d03b0000000000002b560200d13b00000000000035560200d23b0000000000003f560200d33b00000000000049560200d43b00000000000053560200d53b0000000000005d560200d63b00000000000067560200d73b00000000000071560200d83b0000000000007b560200d93b00000000000085560200da3b0000000000008f560200db3b00000000000099560200dc3b000000000000a3560200dd3b000000000000ad560200de3b000000000000b7560200df3b000000000000c1560200e03b000000000000cb560200e13b000000000000d5560200e23b000000000000df560200e33b000000000000e9560200e43b000000000000f3560200e53b000000000000fd560200e63b00000000000007570200e73b00000000000011570200e83b0000000000001b570200e93b00000000000025570200ea3b0000000000002f570200eb3b00000000000039570200ec3b00000000000043570200ed3b0000000000004d570200ee3b00000000000057570200ef3b00000000000061570200f03b0000000000006b570200f13b00000000000075570200f23b0000000000007f570200f33b00000000000089570200f43b00000000000093570200f53b0000000000009d570200f63b000000000000a7570200f73b000000000000b1570200f83b000000000000bb570200f93b000000000000c5570200fa3b000000000000cf570200fb3b000000000000d9570200fc3b000000000000e3570200fd3b000000000000ed570200fe3b000000000000f7570200ff3b00000000000001580200003c0000000000000b580200013c00000000000015580200023c0000000000001f580200033c00000000000029580200043c00000000000033580200053c0000000000003d580200063c00000000000047580200073c00000000000051580200083c0000000000005b580200093c000000000000655802000a3c0000000000006f5802000b3c000000000000795802000c3c000000000000835802000d3c0000000000008d5802000e3c000000000000975802000f3c000000000000a1580200103c000000000000ab580200113c000000000000b5580200123c000000000000bf580200133c000000000000c9580200143c000000000000d3580200153c000000000000dd580200163c000000000000e7580200173c000000000000f1580200183c000000000000fb580200193c000000000000055902001a3c0000000000000f5902001b3c000000000000195902001c3c000000000000235902001d3c0000000000002d5902001e3c000000000000375902001f3c00000000000041590200203c0000000000004b590200213c00000000000055590200223c0000000000005f590200233c00000000000069590200243c00000000000073590200253c0000000000007d590200263c00000000000087590200273c00000000000091590200283c0000000000009b590200293c000000000000a55902002a3c000000000000af5902002b3c000000000000b95902002c3c000000000000c35902002d3c000000000000cd5902002e3c000000000000d75902002f3c000000000000e1590200303c000000000000eb590200313c000000000000f5590200323c000000000000ff590200333c000000000000095a0200343c000000000000135a0200353c0000000000001d5a0200363c000000000000275a0200373c000000000000315a0200383c0000000000003b5a0200393c000000000000455a02003a3c0000000000004f5a02003b3c000000000000595a02003c3c000000000000635a02003d3c0000000000006d5a02003e3c000000000000775a02003f3c000000000000815a0200403c0000000000008b5a0200413c000000000000955a0200423c0000000000009f5a0200433c000000000000a95a0200443c000000000000b35a0200453c000000000000bd5a0200463c000000000000c75a0200473c000000000000d15a0200483c000000000000db5a0200493c000000000000e55a02004a3c000000000000ef5a02004b3c000000000000f95a02004c3c000000000000035b02004d3c0000000000000d5b02004e3c000000000000175b02004f3c000000000000215b0200503c0000000000002b5b0200513c000000000000355b0200523c0000000000003f5b0200533c000000000000495b0200543c000000000000535b0200553c0000000000005d5b0200563c000000000000675b0200573c000000000000715b0200583c0000000000007b5b0200593c000000000000855b02005a3c0000000000008f5b02005b3c000000000000995b02005c3c000000000000a35b02005d3c000000000000ad5b02005e3c000000000000b75b02005f3c000000000000c15b0200603c000000000000cb5b0200613c000000000000d55b0200623c000000000000df5b0200633c000000000000e95b0200643c000000000000f35b0200653c000000000000fd5b0200663c000000000000075c0200673c000000000000115c0200683c0000000000001b5c0200693c000000000000255c02006a3c0000000000002f5c02006b3c000000000000395c02006c3c000000000000435c02006d3c0000000000004d5c02006e3c000000000000575c02006f3c000000000000615c0200703c0000000000006b5c0200713c000000000000755c0200723c0000000000007f5c0200733c000000000000895c0200743c000000000000935c0200753c0000000000009d5c0200763c000000000000a75c0200773c000000000000b15c0200783c000000000000bb5c0200793c000000000000c55c02007a3c000000000000cf5c02007b3c000000000000d95c02007c3c000000000000e35c02007d3c000000000000ed5c02007e3c000000000000f75c02007f3c000000000000015d0200803c0000000000000b5d0200813c000000000000155d0200823c0000000000001f5d0200833c000000000000295d0200843c000000000000335d0200853c0000000000003d5d0200863c000000000000475d0200873c000000000000515d0200883c0000000000005b5d0200893c000000000000655d02008a3c0000000000006f5d02008b3c000000000000795d02008c3c000000000000835d02008d3c0000000000008d5d02008e3c000000000000975d02008f3c000000000000a15d0200903c000000000000ab5d0200913c000000000000b55d0200923c000000000000bf5d0200933c000000000000c95d0200943c000000000000d35d0200953c000000000000dd5d0200963c000000000000e75d0200973c000000000000f15d0200983c000000000000fb5d0200993c000000000000055e02009a3c0000000000000f5e02009b3c000000000000195e02009c3c000000000000235e02009d3c0000000000002d5e02009e3c000000000000375e02009f3c000000000000415e0200a03c0000000000004b5e0200a13c000000000000555e0200a23c0000000000005f5e0200a33c000000000000695e0200a43c000000000000735e0200a53c0000000000007d5e0200a63c000000000000875e0200a73c000000000000915e0200a83c0000000000009b5e0200a93c000000000000a55e0200aa3c000000000000af5e0200ab3c000000000000b95e0200ac3c000000000000c35e0200ad3c000000000000cd5e0200ae3c000000000000d75e0200af3c000000000000e15e0200b03c000000000000eb5e0200b13c000000000000f55e0200b23c000000000000ff5e0200b33c000000000000095f0200b43c000000000000135f0200b53c0000000000001d5f0200b63c000000000000275f0200b73c000000000000315f0200b83c0000000000003b5f0200b93c000000000000455f0200ba3c0000000000004f5f0200bb3c000000000000595f0200bc3c000000000000635f0200bd3c0000000000006d5f0200be3c000000000000775f0200bf3c000000000000815f0200c03c0000000000008b5f0200c13c000000000000955f0200c23c0000000000009f5f0200c33c000000000000a95f0200c43c000000000000b35f0200c53c000000000000bd5f0200c63c000000000000c75f0200c73c000000000000d15f0200c83c000000000000db5f0200c93c000000000000e55f0200ca3c000000000000ef5f0200cb3c000000000000f95f0200cc3c00000000000003600200cd3c0000000000000d600200ce3c00000000000017600200cf3c00000000000021600200d03c0000000000002b600200d13c00000000000035600200d23c0000000000003f600200d33c00000000000049600200d43c00000000000053600200d53c0000000000005d600200d63c00000000000067600200d73c00000000000071600200d83c0000000000007b600200d93c00000000000085600200da3c0000000000008f600200db3c00000000000099600200dc3c000000000000a3600200dd3c000000000000ad600200de3c000000000000b7600200df3c000000000000c1600200e03c000000000000cb600200e13c000000000000d5600200e23c000000000000df600200e33c000000000000e9600200e43c000000000000f3600200e53c000000000000fd600200e63c00000000000007610200e73c00000000000011610200e83c0000000000001b610200e93c00000000000025610200ea3c0000000000002f610200eb3c00000000000039610200ec3c00000000000043610200ed3c0000000000004d610200ee3c00000000000057610200ef3c00000000000061610200f03c0000000000006b610200f13c00000000000075610200f23c0000000000007f610200f33c00000000000089610200f43c00000000000093610200f53c0000000000009d610200f63c000000000000a7610200f73c000000000000b1610200f83c000000000000bb610200f93c000000000000c5610200fa3c000000000000cf610200fb3c000000000000d9610200fc3c000000000000e3610200fd3c000000000000ed610200fe3c000000000000f7610200ff3c00000000000001620200003d0000000000000b620200013d00000000000015620200023d0000000000001f620200033d00000000000029620200043d00000000000033620200053d0000000000003d620200063d00000000000047620200073d00000000000051620200083d0000000000005b620200093d000000000000656202000a3d0000000000006f6202000b3d000000000000796202000c3d000000000000836202000d3d0000000000008d6202000e3d000000000000976202000f3d000000000000a1620200103d000000000000ab620200113d000000000000b5620200123d000000000000bf620200133d000000000000c9620200143d000000000000d3620200153d000000000000dd620200163d000000000000e7620200173d000000000000f1620200183d000000000000fb620200193d000000000000056302001a3d0000000000000f6302001b3d000000000000196302001c3d000000000000236302001d3d0000000000002d6302001e3d000000000000376302001f3d00000000000041630200203d0000000000004b630200213d00000000000055630200223d0000000000005f630200233d00000000000069630200243d00000000000073630200253d0000000000007d630200263d00000000000087630200273d00000000000091630200283d0000000000009b630200293d000000000000a56302002a3d000000000000af6302002b3d000000000000b96302002c3d000000000000c36302002d3d000000000000cd6302002e3d000000000000d76302002f3d000000000000e1630200303d000000000000eb630200313d000000000000f5630200323d000000000000ff630200333d00000000000009640200343d00000000000013640200353d0000000000001d640200363d00000000000027640200373d00000000000031640200383d0000000000003b640200393d000000000000456402003a3d0000000000004f6402003b3d000000000000596402003c3d000000000000636402003d3d0000000000006d6402003e3d000000000000776402003f3d00000000000081640200403d0000000000008b640200413d00000000000095640200423d0000000000009f640200433d000000000000a9640200443d000000000000b3640200453d000000000000bd640200463d000000000000c7640200473d000000000000d1640200483d000000000000db640200493d000000000000e56402004a3d000000000000ef6402004b3d000000000000f96402004c3d000000000000036502004d3d0000000000000d6502004e3d000000000000176502004f3d00000000000021650200503d0000000000002b650200513d00000000000035650200523d0000000000003f650200533d00000000000049650200543d00000000000053650200553d0000000000005d650200563d00000000000067650200573d00000000000071650200583d0000000000007b650200593d000000000000856502005a3d0000000000008f6502005b3d000000000000996502005c3d000000000000a36502005d3d000000000000ad6502005e3d000000000000b76502005f3d000000000000c1650200603d000000000000cb650200613d000000000000d5650200623d000000000000df650200633d000000000000e9650200643d000000000000f3650200653d000000000000fd650200663d00000000000007660200673d00000000000011660200683d0000000000001b660200693d000000000000256602006a3d0000000000002f6602006b3d000000000000396602006c3d000000000000436602006d3d0000000000004d6602006e3d000000000000576602006f3d00000000000061660200703d0000000000006b660200713d00000000000075660200723d0000000000007f660200733d00000000000089660200743d00000000000093660200753d0000000000009d660200763d000000000000a7660200773d000000000000b1660200783d000000000000bb660200793d000000000000c56602007a3d000000000000cf6602007b3d000000000000d96602007c3d000000000000e36602007d3d000000000000ed6602007e3d000000000000f76602007f3d00000000000001670200803d0000000000000b670200813d00000000000015670200823d0000000000001f670200833d00000000000029670200843d00000000000033670200853d0000000000003d670200863d00000000000047670200873d00000000000051670200883d0000000000005b670200893d000000000000656702008a3d0000000000006f6702008b3d000000000000796702008c3d000000000000836702008d3d0000000000008d6702008e3d000000000000976702008f3d000000000000a1670200903d000000000000ab670200913d000000000000b5670200923d000000000000bf670200933d000000000000c9670200943d000000000000d3670200953d000000000000dd670200963d000000000000e7670200973d000000000000f1670200983d000000000000fb670200993d000000000000056802009a3d0000000000000f6802009b3d000000000000196802009c3d000000000000236802009d3d0000000000002d6802009e3d000000000000376802009f3d00000000000041680200a03d0000000000004b680200a13d00000000000055680200a23d0000000000005f680200a33d00000000000069680200a43d00000000000073680200a53d0000000000007d680200a63d00000000000087680200a73d00000000000091680200a83d0000000000009b680200a93d000000000000a5680200aa3d000000000000af680200ab3d000000000000b9680200ac3d000000000000c3680200ad3d000000000000cd680200ae3d000000000000d7680200af3d000000000000e1680200b03d000000000000eb680200b13d000000000000f5680200b23d000000000000ff680200b33d00000000000009690200b43d00000000000013690200b53d0000000000001d690200b63d00000000000027690200b73d00000000000031690200b83d0000000000003b690200b93d00000000000045690200ba3d0000000000004f690200bb3d00000000000059690200bc3d00000000000063690200bd3d0000000000006d690200be3d00000000000077690200bf3d00000000000081690200c03d0000000000008b690200c13d00000000000095690200c23d0000000000009f690200c33d000000000000a9690200c43d000000000000b3690200c53d000000000000bd690200c63d000000000000c7690200c73d000000000000d1690200c83d000000000000db690200c93d000000000000e5690200ca3d000000000000ef690200cb3d000000000000f9690200cc3d000000000000036a0200cd3d0000000000000d6a0200ce3d000000000000176a0200cf3d000000000000216a0200d03d0000000000002b6a0200d13d000000000000356a0200d23d0000000000003f6a0200d33d000000000000496a0200d43d000000000000536a0200d53d0000000000005d6a0200d63d000000000000676a0200d73d000000000000716a0200d83d0000000000007b6a0200d93d000000000000856a0200da3d0000000000008f6a0200db3d000000000000996a0200dc3d000000000000a36a0200dd3d000000000000ad6a0200de3d000000000000b76a0200df3d000000000000c16a0200e03d000000000000cb6a0200e13d000000000000d56a0200e23d000000000000df6a0200e33d000000000000e96a0200e43d000000000000f36a0200e53d000000000000fd6a0200e63d000000000000076b0200e73d000000000000116b0200e83d0000000000001b6b0200e93d000000000000256b0200ea3d0000000000002f6b0200eb3d000000000000396b0200ec3d000000000000436b0200ed3d0000000000004d6b0200ee3d000000000000576b0200ef3d000000000000616b0200f03d0000000000006b6b0200f13d000000000000756b0200f23d0000000000007f6b0200f33d000000000000896b0200f43d000000000000936b0200f53d0000000000009d6b0200f63d000000000000a76b0200f73d000000000000b16b0200f83d000000000000bb6b0200f93d000000000000c56b0200fa3d000000000000cf6b0200fb3d000000000000d96b0200fc3d000000000000e36b0200fd3d000000000000ed6b0200fe3d000000000000f76b0200ff3d000000000000016c0200003e0000000000000b6c0200013e000000000000156c0200023e0000000000001f6c0200033e000000000000296c0200043e000000000000336c0200053e0000000000003d6c0200063e000000000000476c0200073e000000000000516c0200083e0000000000005b6c0200093e000000000000656c02000a3e0000000000006f6c02000b3e000000000000796c02000c3e000000000000836c02000d3e0000000000008d6c02000e3e000000000000976c02000f3e000000000000a16c0200103e000000000000ab6c0200113e000000000000b56c0200123e000000000000bf6c0200133e000000000000c96c0200143e000000000000d36c0200153e000000000000dd6c0200163e000000000000e76c0200173e000000000000f16c0200183e000000000000fb6c0200193e000000000000056d02001a3e0000000000000f6d02001b3e000000000000196d02001c3e000000000000236d02001d3e0000000000002d6d02001e3e000000000000376d02001f3e000000000000416d0200203e0000000000004b6d0200213e000000000000556d0200223e0000000000005f6d0200233e000000000000696d0200243e000000000000736d0200253e0000000000007d6d0200263e000000000000876d0200273e000000000000916d0200283e0000000000009b6d0200293e000000000000a56d02002a3e000000000000af6d02002b3e000000000000b96d02002c3e000000000000c36d02002d3e000000000000cd6d02002e3e000000000000d76d02002f3e000000000000e16d0200303e000000000000eb6d0200313e000000000000f56d0200323e000000000000ff6d0200333e000000000000096e0200343e000000000000136e0200353e0000000000001d6e0200363e000000000000276e0200373e000000000000316e0200383e0000000000003b6e0200393e000000000000456e02003a3e0000000000004f6e02003b3e000000000000596e02003c3e000000000000636e02003d3e0000000000006d6e02003e3e000000000000776e02003f3e000000000000816e0200403e0000000000008b6e0200413e000000000000956e0200423e0000000000009f6e0200433e000000000000a96e0200443e000000000000b36e0200453e000000000000bd6e0200463e000000000000c76e0200473e000000000000d16e0200483e000000000000db6e0200493e000000000000e56e02004a3e000000000000ef6e02004b3e000000000000f96e02004c3e000000000000036f02004d3e0000000000000d6f02004e3e000000000000176f02004f3e000000000000216f0200503e0000000000002b6f0200513e000000000000356f0200523e0000000000003f6f0200533e000000000000496f0200543e000000000000536f0200553e0000000000005d6f0200563e000000000000676f0200573e000000000000716f0200583e0000000000007b6f0200593e000000000000856f02005a3e0000000000008f6f02005b3e000000000000996f02005c3e000000000000a36f02005d3e000000000000ad6f02005e3e000000000000b76f02005f3e000000000000c16f0200603e000000000000cb6f0200613e000000000000d56f0200623e000000000000df6f0200633e000000000000e96f0200643e000000000000f36f0200653e000000000000fd6f0200663e00000000000007700200673e00000000000011700200683e0000000000001b700200693e000000000000257002006a3e0000000000002f7002006b3e000000000000397002006c3e000000000000437002006d3e0000000000004d7002006e3e000000000000577002006f3e00000000000061700200703e0000000000006b700200713e00000000000075700200723e0000000000007f700200733e00000000000089700200743e00000000000093700200753e0000000000009d700200763e000000000000a7700200773e000000000000b1700200783e000000000000bb700200793e000000000000c57002007a3e000000000000cf7002007b3e000000000000d97002007c3e000000000000e37002007d3e000000000000ed7002007e3e000000000000f77002007f3e00000000000001710200803e0000000000000b710200813e00000000000015710200823e0000000000001f710200833e00000000000029710200843e00000000000033710200853e0000000000003d710200863e00000000000047710200873e00000000000051710200883e0000000000005b710200893e000000000000657102008a3e0000000000006f7102008b3e000000000000797102008c3e000000000000837102008d3e0000000000008d7102008e3e000000000000977102008f3e000000000000a1710200903e000000000000ab710200913e000000000000b5710200923e000000000000bf710200933e000000000000c9710200943e000000000000d3710200953e000000000000dd710200963e000000000000e7710200973e000000000000f1710200983e000000000000fb710200993e000000000000057202009a3e0000000000000f7202009b3e000000000000197202009c3e000000000000237202009d3e0000000000002d7202009e3e000000000000377202009f3e00000000000041720200a03e0000000000004b720200a13e00000000000055720200a23e0000000000005f720200a33e00000000000069720200a43e00000000000073720200a53e0000000000007d720200a63e00000000000087720200a73e00000000000091720200a83e0000000000009b720200a93e000000000000a5720200aa3e000000000000af720200ab3e000000000000b9720200ac3e000000000000c3720200ad3e000000000000cd720200ae3e000000000000d7720200af3e000000000000e1720200b03e000000000000eb720200b13e000000000000f5720200b23e000000000000ff720200b33e00000000000009730200b43e00000000000013730200b53e0000000000001d730200b63e00000000000027730200b73e00000000000031730200b83e0000000000003b730200b93e00000000000045730200ba3e0000000000004f730200bb3e00000000000059730200bc3e00000000000063730200bd3e0000000000006d730200be3e00000000000077730200bf3e00000000000081730200c03e0000000000008b730200c13e00000000000095730200c23e0000000000009f730200c33e000000000000a9730200c43e000000000000b3730200c53e000000000000bd730200c63e000000000000c7730200c73e000000000000d1730200c83e000000000000db730200c93e000000000000e5730200ca3e000000000000ef730200cb3e000000000000f9730200cc3e00000000000003740200cd3e0000000000000d740200ce3e00000000000017740200cf3e00000000000021740200d03e0000000000002b740200d13e00000000000035740200d23e0000000000003f740200d33e00000000000049740200d43e00000000000053740200d53e0000000000005d740200d63e00000000000067740200d73e00000000000071740200d83e0000000000007b740200d93e00000000000085740200da3e0000000000008f740200db3e00000000000099740200dc3e000000000000a3740200dd3e000000000000ad740200de3e000000000000b7740200df3e000000000000c1740200e03e000000000000cb740200e13e000000000000d5740200e23e000000000000df740200e33e000000000000e9740200e43e000000000000f3740200e53e000000000000fd740200e63e00000000000007750200e73e00000000000011750200e83e0000000000001b750200e93e00000000000025750200ea3e0000000000002f750200eb3e00000000000039750200ec3e00000000000043750200ed3e0000000000004d750200ee3e00000000000057750200ef3e00000000000061750200f03e0000000000006b750200f13e00000000000075750200f23e0000000000007f750200f33e00000000000089750200f43e00000000000093750200f53e0000000000009d750200f63e000000000000a7750200f73e000000000000b1750200f83e000000000000bb750200f93e000000000000c5750200fa3e000000000000cf750200fb3e000000000000d9750200fc3e000000000000e3750200fd3e000000000000ed750200fe3e000000000000f7750200ff3e00000000000001760200003f0000000000000b760200013f00000000000015760200023f0000000000001f760200033f00000000000029760200043f00000000000033760200053f0000000000003d760200063f00000000000047760200073f00000000000051760200083f0000000000005b760200093f000000000000657602000a3f0000000000006f7602000b3f000000000000797602000c3f000000000000837602000d3f0000000000008d7602000e3f000000000000977602000f3f000000000000a1760200103f000000000000ab760200113f000000000000b5760200123f000000000000bf760200133f000000000000c9760200143f000000000000d3760200153f000000000000dd760200163f000000000000e7760200173f000000000000f1760200183f000000000000fb760200193f000000000000057702001a3f0000000000000f7702001b3f000000000000197702001c3f000000000000237702001d3f0000000000002d7702001e3f000000000000377702001f3f00000000000041770200203f0000000000004b770200213f00000000000055770200223f0000000000005f770200233f00000000000069770200243f00000000000073770200253f0000000000007d770200263f00000000000087770200273f00000000000091770200283f0000000000009b770200293f000000000000a57702002a3f000000000000af7702002b3f000000000000b97702002c3f000000000000c37702002d3f000000000000cd7702002e3f000000000000d77702002f3f000000000000e1770200303f000000000000eb770200313f000000000000f5770200323f000000000000ff770200333f00000000000009780200343f00000000000013780200353f0000000000001d780200363f00000000000027780200373f00000000000031780200383f0000000000003b780200393f000000000000457802003a3f0000000000004f7802003b3f000000000000597802003c3f000000000000637802003d3f0000000000006d7802003e3f000000000000777802003f3f00000000000081780200403f0000000000008b780200413f00000000000095780200423f0000000000009f780200433f000000000000a9780200443f000000000000b3780200453f000000000000bd780200463f000000000000c7780200473f000000000000d1780200483f000000000000db780200493f000000000000e57802004a3f000000000000ef7802004b3f000000000000f97802004c3f000000000000037902004d3f0000000000000d7902004e3f000000000000177902004f3f00000000000021790200503f0000000000002b790200513f00000000000035790200523f0000000000003f790200533f00000000000049790200543f00000000000053790200553f0000000000005d790200563f00000000000067790200573f00000000000071790200583f0000000000007b790200593f000000000000857902005a3f0000000000008f7902005b3f000000000000997902005c3f000000000000a37902005d3f000000000000ad7902005e3f000000000000b77902005f3f000000000000c1790200603f000000000000cb790200613f000000000000d5790200623f000000000000df790200633f000000000000e9790200643f000000000000f3790200653f000000000000fd790200663f000000000000077a0200673f000000000000117a0200683f0000000000001b7a0200693f000000000000257a02006a3f0000000000002f7a02006b3f000000000000397a02006c3f000000000000437a02006d3f0000000000004d7a02006e3f000000000000577a02006f3f000000000000617a0200703f0000000000006b7a0200713f000000000000757a0200723f0000000000007f7a0200733f000000000000897a0200743f000000000000937a0200753f0000000000009d7a0200763f000000000000a77a0200773f000000000000b17a0200783f000000000000bb7a0200793f000000000000c57a02007a3f000000000000cf7a02007b3f000000000000d97a02007c3f000000000000e37a02007d3f000000000000ed7a02007e3f000000000000f77a02007f3f000000000000017b0200803f0000000000000b7b0200813f000000000000157b0200823f0000000000001f7b0200833f000000000000297b0200843f000000000000337b0200853f0000000000003d7b0200863f000000000000477b0200873f000000000000517b0200883f0000000000005b7b0200893f000000000000657b02008a3f0000000000006f7b02008b3f000000000000797b02008c3f000000000000837b02008d3f0000000000008d7b02008e3f000000000000977b02008f3f000000000000a17b0200903f000000000000ab7b0200913f000000000000b57b0200923f000000000000bf7b0200933f000000000000c97b0200943f000000000000d37b0200953f000000000000dd7b0200963f000000000000e77b0200973f000000000000f17b0200983f000000000000fb7b0200993f000000000000057c02009a3f0000000000000f7c02009b3f000000000000197c02009c3f000000000000237c02009d3f0000000000002d7c02009e3f000000000000377c02009f3f000000000000417c0200a03f0000000000004b7c0200a13f000000000000557c0200a23f0000000000005f7c0200a33f000000000000697c0200a43f000000000000737c0200a53f0000000000007d7c0200a63f000000000000877c0200a73f000000000000917c0200a83f0000000000009b7c0200a93f000000000000a57c0200aa3f000000000000af7c0200ab3f000000000000b97c0200ac3f000000000000c37c0200ad3f000000000000cd7c0200ae3f000000000000d77c0200af3f000000000000e17c0200b03f000000000000eb7c0200b13f000000000000f57c0200b23f000000000000ff7c0200b33f000000000000097d0200b43f000000000000137d0200b53f0000000000001d7d0200b63f000000000000277d0200b73f000000000000317d0200b83f0000000000003b7d0200b93f000000000000457d0200ba3f0000000000004f7d0200bb3f000000000000597d0200bc3f000000000000637d0200bd3f0000000000006d7d0200be3f000000000000777d0200bf3f000000000000817d0200c03f0000000000008b7d0200c13f000000000000957d0200c23f0000000000009f7d0200c33f000000000000a97d0200c43f000000000000b37d0200c53f000000000000bd7d0200c63f000000000000c77d0200c73f000000000000d17d0200c83f000000000000db7d0200c93f000000000000e57d0200ca3f000000000000ef7d0200cb3f000000000000f97d0200cc3f000000000000037e0200cd3f0000000000000d7e0200ce3f000000000000177e0200cf3f000000000000217e0200d03f0000000000002b7e0200d13f000000000000357e0200d23f0000000000003f7e0200d33f000000000000497e0200d43f000000000000537e0200d53f0000000000005d7e0200d63f000000000000677e0200d73f000000000000717e0200d83f0000000000007b7e0200d93f000000000000857e0200da3f0000000000008f7e0200db3f000000000000997e0200dc3f000000000000a37e0200dd3f000000000000ad7e0200de3f000000000000b77e0200df3f000000000000c17e0200e03f000000000000cb7e0200e13f000000000000d57e0200e23f000000000000df7e0200e33f000000000000e97e0200e43f000000000000f37e0200e53f000000000000fd7e0200e63f000000000000077f0200e73f000000000000117f0200e83f0000000000001b7f0200e93f000000000000257f0200ea3f0000000000002f7f0200eb3f000000000000397f0200ec3f000000000000437f0200ed3f0000000000004d7f0200ee3f000000000000577f0200ef3f000000000000617f0200f03f0000000000006b7f0200f13f000000000000757f0200f23f0000000000007f7f0200f33f000000000000897f0200f43f000000000000937f0200f53f0000000000009d7f0200f63f000000000000a77f0200f73f000000000000b17f0200f83f000000000000bb7f0200f93f000000000000c57f0200fa3f000000000000cf7f0200fb3f000000000000d97f0200fc3f000000000000e37f0200fd3f000000000000ed7f0200fe3f000000000000f77f0200ff3f0000000000000180020000400000000000000b80020001400000000000001580020002400000000000001f80020003400000000000002980020004400000000000003380020005400000000000003d80020006400000000000004780020007400000000000005180020008400000000000005b8002000940000000000000658002000a400000000000006f8002000b40000000000000798002000c40000000000000838002000d400000000000008d8002000e40000000000000978002000f40000000000000a18002001040000000000000ab8002001140000000000000b58002001240000000000000bf8002001340000000000000c98002001440000000000000d38002001540000000000000dd8002001640000000000000e78002001740000000000000f18002001840000000000000fb8002001940000000000000058102001a400000000000000f8102001b40000000000000198102001c40000000000000238102001d400000000000002d8102001e40000000000000378102001f400000000000004181020020400000000000004b81020021400000000000005581020022400000000000005f81020023400000000000006981020024400000000000007381020025400000000000007d81020026400000000000008781020027400000000000009181020028400000000000009b8102002940000000000000a58102002a40000000000000af8102002b40000000000000b98102002c40000000000000c38102002d40000000000000cd8102002e40000000000000d78102002f40000000000000e18102003040000000000000eb8102003140000000000000f58102003240000000000000ff81020033400000000000000982020034400000000000001382020035400000000000001d82020036400000000000002782020037400000000000003182020038400000000000003b8202003940000000000000458202003a400000000000004f8202003b40000000000000598202003c40000000000000638202003d400000000000006d8202003e40000000000000778202003f400000000000008182020040400000000000008b82020041400000000000009582020042400000000000009f8202004340000000000000a98202004440000000000000b38202004540000000000000bd8202004640000000000000c78202004740000000000000d18202004840000000000000db8202004940000000000000e58202004a40000000000000ef8202004b40000000000000f98202004c40000000000000038302004d400000000000000d8302004e40000000000000178302004f400000000000002183020050400000000000002b83020051400000000000003583020052400000000000003f83020053400000000000004983020054400000000000005383020055400000000000005d83020056400000000000006783020057400000000000007183020058400000000000007b8302005940000000000000858302005a400000000000008f8302005b40000000000000998302005c40000000000000a38302005d40000000000000ad8302005e40000000000000b78302005f40000000000000c18302006040000000000000cb8302006140000000000000d58302006240000000000000df8302006340000000000000e98302006440000000000000f38302006540000000000000fd83020066400000000000000784020067400000000000001184020068400000000000001b8402006940000000000000258402006a400000000000002f8402006b40000000000000398402006c40000000000000438402006d400000000000004d8402006e40000000000000578402006f400000000000006184020070400000000000006b84020071400000000000007584020072400000000000007f84020073400000000000008984020074400000000000009384020075400000000000009d8402007640000000000000a78402007740000000000000b18402007840000000000000bb8402007940000000000000c58402007a40000000000000cf8402007b40000000000000d98402007c40000000000000e38402007d40000000000000ed8402007e40000000000000f78402007f400000000000000185020080400000000000000b85020081400000000000001585020082400000000000001f85020083400000000000002985020084400000000000003385020085400000000000003d85020086400000000000004785020087400000000000005185020088400000000000005b8502008940000000000000658502008a400000000000006f8502008b40000000000000798502008c40000000000000838502008d400000000000008d8502008e40000000000000978502008f40000000000000a18502009040000000000000ab8502009140000000000000b58502009240000000000000bf8502009340000000000000c98502009440000000000000d38502009540000000000000dd8502009640000000000000e78502009740000000000000f18502009840000000000000fb8502009940000000000000058602009a400000000000000f8602009b40000000000000198602009c40000000000000238602009d400000000000002d8602009e40000000000000378602009f4000000000000041860200a0400000000000004b860200a14000000000000055860200a2400000000000005f860200a34000000000000069860200a44000000000000073860200a5400000000000007d860200a64000000000000087860200a74000000000000091860200a8400000000000009b860200a940000000000000a5860200aa40000000000000af860200ab40000000000000b9860200ac40000000000000c3860200ad40000000000000cd860200ae40000000000000d7860200af40000000000000e1860200b040000000000000eb860200b140000000000000f5860200b240000000000000ff860200b34000000000000009870200b44000000000000013870200b5400000000000001d870200b64000000000000027870200b74000000000000031870200b8400000000000003b870200b94000000000000045870200ba400000000000004f870200bb4000000000000059870200bc4000000000000063870200bd400000000000006d870200be4000000000000077870200bf4000000000000081870200c0400000000000008b870200c14000000000000095870200c2400000000000009f870200c340000000000000a9870200c440000000000000b3870200c540000000000000bd870200c640000000000000c7870200c740000000000000d1870200c840000000000000db870200c940000000000000e5870200ca40000000000000ef870200cb40000000000000f9870200cc4000000000000003880200cd400000000000000d880200ce4000000000000017880200cf4000000000000021880200d0400000000000002b880200d14000000000000035880200d2400000000000003f880200d34000000000000049880200d44000000000000053880200d5400000000000005d880200d64000000000000067880200d74000000000000071880200d8400000000000007b880200d94000000000000085880200da400000000000008f880200db4000000000000099880200dc40000000000000a3880200dd40000000000000ad880200de40000000000000b7880200df40000000000000c1880200e040000000000000cb880200e140000000000000d5880200e240000000000000df880200e340000000000000e9880200e440000000000000f3880200e540000000000000fd880200e64000000000000007890200e74000000000000011890200e8400000000000001b890200e94000000000000025890200ea400000000000002f890200eb4000000000000039890200ec4000000000000043890200ed400000000000004d890200ee4000000000000057890200ef4000000000000061890200f0400000000000006b890200f14000000000000075890200f2400000000000007f890200f34000000000000089890200f44000000000000093890200f5400000000000009d890200f640000000000000a7890200f740000000000000b1890200f840000000000000bb890200f940000000000000c5890200fa40000000000000cf890200fb40000000000000d9890200fc40000000000000e3890200fd40000000000000ed890200fe40000000000000f7890200ff40000000000000018a020000410000000000000b8a02000141000000000000158a020002410000000000001f8a02000341000000000000298a02000441000000000000338a020005410000000000003d8a02000641000000000000478a02000741000000000000518a020008410000000000005b8a02000941000000000000658a02000a410000000000006f8a02000b41000000000000798a02000c41000000000000838a02000d410000000000008d8a02000e41000000000000978a02000f41000000000000a18a02001041000000000000ab8a02001141000000000000b58a02001241000000000000bf8a02001341000000000000c98a02001441000000000000d38a02001541000000000000dd8a02001641000000000000e78a02001741000000000000f18a02001841000000000000fb8a02001941000000000000058b02001a410000000000000f8b02001b41000000000000198b02001c41000000000000238b02001d410000000000002d8b02001e41000000000000378b02001f41000000000000418b020020410000000000004b8b02002141000000000000558b020022410000000000005f8b02002341000000000000698b02002441000000000000738b020025410000000000007d8b02002641000000000000878b02002741000000000000918b020028410000000000009b8b02002941000000000000a58b02002a41000000000000af8b02002b41000000000000b98b02002c41000000000000c38b02002d41000000000000cd8b02002e41000000000000d78b02002f41000000000000e18b02003041000000000000eb8b02003141000000000000f58b02003241000000000000ff8b02003341000000000000098c02003441000000000000138c020035410000000000001d8c02003641000000000000278c02003741000000000000318c020038410000000000003b8c02003941000000000000458c02003a410000000000004f8c02003b41000000000000598c02003c41000000000000638c02003d410000000000006d8c02003e41000000000000778c02003f41000000000000818c020040410000000000008b8c02004141000000000000958c020042410000000000009f8c02004341000000000000a98c02004441000000000000b38c02004541000000000000bd8c02004641000000000000c78c02004741000000000000d18c02004841000000000000db8c02004941000000000000e58c02004a41000000000000ef8c02004b41000000000000f98c02004c41000000000000038d02004d410000000000000d8d02004e41000000000000178d02004f41000000000000218d020050410000000000002b8d02005141000000000000358d020052410000000000003f8d02005341000000000000498d02005441000000000000538d020055410000000000005d8d02005641000000000000678d02005741000000000000718d020058410000000000007b8d02005941000000000000858d02005a410000000000008f8d02005b41000000000000998d02005c41000000000000a38d02005d41000000000000ad8d02005e41000000000000b78d02005f41000000000000c18d02006041000000000000cb8d02006141000000000000d58d02006241000000000000df8d02006341000000000000e98d02006441000000000000f38d02006541000000000000fd8d02006641000000000000078e02006741000000000000118e020068410000000000001b8e02006941000000000000258e02006a410000000000002f8e02006b41000000000000398e02006c41000000000000438e02006d410000000000004d8e02006e41000000000000578e02006f41000000000000618e020070410000000000006b8e02007141000000000000758e020072410000000000007f8e02007341000000000000898e02007441000000000000938e020075410000000000009d8e02007641000000000000a78e02007741000000000000b18e02007841000000000000bb8e02007941000000000000c58e02007a41000000000000cf8e02007b41000000000000d98e02007c41000000000000e38e02007d41000000000000ed8e02007e41000000000000f78e02007f41000000000000018f020080410000000000000b8f02008141000000000000158f020082410000000000001f8f02008341000000000000298f02008441000000000000338f020085410000000000003d8f02008641000000000000478f02008741000000000000518f020088410000000000005b8f02008941000000000000658f02008a410000000000006f8f02008b41000000000000798f02008c41000000000000838f02008d410000000000008d8f02008e41000000000000978f02008f41000000000000a18f02009041000000000000ab8f02009141000000000000b58f02009241000000000000bf8f02009341000000000000c98f02009441000000000000d38f02009541000000000000dd8f02009641000000000000e78f02009741000000000000f18f02009841000000000000fb8f02009941000000000000059002009a410000000000000f9002009b41000000000000199002009c41000000000000239002009d410000000000002d9002009e41000000000000379002009f4100000000000041900200a0410000000000004b900200a14100000000000055900200a2410000000000005f900200a34100000000000069900200a44100000000000073900200a5410000000000007d900200a64100000000000087900200a74100000000000091900200a8410000000000009b900200a941000000000000a5900200aa41000000000000af900200ab41000000000000b9900200ac41000000000000c3900200ad41000000000000cd900200ae41000000000000d7900200af41000000000000e1900200b041000000000000eb900200b141000000000000f5900200b241000000000000ff900200b34100000000000009910200b44100000000000013910200b5410000000000001d910200b64100000000000027910200b74100000000000031910200b8410000000000003b910200b94100000000000045910200ba410000000000004f910200bb4100000000000059910200bc4100000000000063910200bd410000000000006d910200be4100000000000077910200bf4100000000000081910200c0410000000000008b910200c14100000000000095910200c2410000000000009f910200c341000000000000a9910200c441000000000000b3910200c541000000000000bd910200c641000000000000c7910200c741000000000000d1910200c841000000000000db910200c941000000000000e5910200ca41000000000000ef910200cb41000000000000f9910200cc4100000000000003920200cd410000000000000d920200ce4100000000000017920200cf4100000000000021920200d0410000000000002b920200d14100000000000035920200d2410000000000003f920200d34100000000000049920200d44100000000000053920200d5410000000000005d920200d64100000000000067920200d74100000000000071920200d8410000000000007b920200d94100000000000085920200da410000000000008f920200db4100000000000099920200dc41000000000000a3920200dd41000000000000ad920200de41000000000000b7920200df41000000000000c1920200e041000000000000cb920200e141000000000000d5920200e241000000000000df920200e341000000000000e9920200e441000000000000f3920200e541000000000000fd920200e64100000000000007930200e74100000000000011930200e8410000000000001b930200e94100000000000025930200ea410000000000002f930200eb4100000000000039930200ec4100000000000043930200ed410000000000004d930200ee4100000000000057930200ef4100000000000061930200f0410000000000006b930200f14100000000000075930200f2410000000000007f930200f34100000000000089930200f44100000000000093930200f5410000000000009d930200f641000000000000a7930200f741000000000000b1930200f841000000000000bb930200f941000000000000c5930200fa41000000000000cf930200fb41000000000000d9930200fc41000000000000e3930200fd41000000000000ed930200fe41000000000000f7930200ff410000000000000194020000420000000000000b94020001420000000000001594020002420000000000001f94020003420000000000002994020004420000000000003394020005420000000000003d94020006420000000000004794020007420000000000005194020008420000000000005b9402000942000000000000659402000a420000000000006f9402000b42000000000000799402000c42000000000000839402000d420000000000008d9402000e42000000000000979402000f42000000000000a19402001042000000000000ab9402001142000000000000b59402001242000000000000bf9402001342000000000000c99402001442000000000000d39402001542000000000000dd9402001642000000000000e79402001742000000000000f19402001842000000000000fb9402001942000000000000059502001a420000000000000f9502001b42000000000000199502001c42000000000000239502001d420000000000002d9502001e42000000000000379502001f420000000000004195020020420000000000004b95020021420000000000005595020022420000000000005f95020023420000000000006995020024420000000000007395020025420000000000007d95020026420000000000008795020027420000000000009195020028420000000000009b9502002942000000000000a59502002a42000000000000af9502002b42000000000000b99502002c42000000000000c39502002d42000000000000cd9502002e42000000000000d79502002f42000000000000e19502003042000000000000eb9502003142000000000000f59502003242000000000000ff95020033420000000000000996020034420000000000001396020035420000000000001d96020036420000000000002796020037420000000000003196020038420000000000003b9602003942000000000000459602003a420000000000004f9602003b42000000000000599602003c42000000000000639602003d420000000000006d9602003e42000000000000779602003f420000000000008196020040420000000000008b96020041420000000000009596020042420000000000009f9602004342000000000000a99602004442000000000000b39602004542000000000000bd9602004642000000000000c79602004742000000000000d19602004842000000000000db9602004942000000000000e59602004a42000000000000ef9602004b42000000000000f99602004c42000000000000039702004d420000000000000d9702004e42000000000000179702004f420000000000002197020050420000000000002b97020051420000000000003597020052420000000000003f97020053420000000000004997020054420000000000005397020055420000000000005d97020056420000000000006797020057420000000000007197020058420000000000007b9702005942000000000000859702005a420000000000008f9702005b42000000000000999702005c42000000000000a39702005d42000000000000ad9702005e42000000000000b79702005f42000000000000c19702006042000000000000cb9702006142000000000000d59702006242000000000000df9702006342000000000000e99702006442000000000000f39702006542000000000000fd97020066420000000000000798020067420000000000001198020068420000000000001b9802006942000000000000259802006a420000000000002f9802006b42000000000000399802006c42000000000000439802006d420000000000004d9802006e42000000000000579802006f420000000000006198020070420000000000006b98020071420000000000007598020072420000000000007f98020073420000000000008998020074420000000000009398020075420000000000009d9802007642000000000000a79802007742000000000000b19802007842000000000000bb9802007942000000000000c59802007a42000000000000cf9802007b42000000000000d99802007c42000000000000e39802007d42000000000000ed9802007e42000000000000f79802007f420000000000000199020080420000000000000b99020081420000000000001599020082420000000000001f99020083420000000000002999020084420000000000003399020085420000000000003d99020086420000000000004799020087420000000000005199020088420000000000005b9902008942000000000000659902008a420000000000006f9902008b42000000000000799902008c42000000000000839902008d420000000000008d9902008e42000000000000979902008f42000000000000a19902009042000000000000ab9902009142000000000000b59902009242000000000000bf9902009342000000000000c99902009442000000000000d39902009542000000000000dd9902009642000000000000e79902009742000000000000f19902009842000000000000fb9902009942000000000000059a02009a420000000000000f9a02009b42000000000000199a02009c42000000000000239a02009d420000000000002d9a02009e42000000000000379a02009f42000000000000419a0200a0420000000000004b9a0200a142000000000000559a0200a2420000000000005f9a0200a342000000000000699a0200a442000000000000739a0200a5420000000000007d9a0200a642000000000000879a0200a742000000000000919a0200a8420000000000009b9a0200a942000000000000a59a0200aa42000000000000af9a0200ab42000000000000b99a0200ac42000000000000c39a0200ad42000000000000cd9a0200ae42000000000000d79a0200af42000000000000e19a0200b042000000000000eb9a0200b142000000000000f59a0200b242000000000000ff9a0200b342000000000000099b0200b442000000000000139b0200b5420000000000001d9b0200b642000000000000279b0200b742000000000000319b0200b8420000000000003b9b0200b942000000000000459b0200ba420000000000004f9b0200bb42000000000000599b0200bc42000000000000639b0200bd420000000000006d9b0200be42000000000000779b0200bf42000000000000819b0200c0420000000000008b9b0200c142000000000000959b0200c2420000000000009f9b0200c342000000000000a99b0200c442000000000000b39b0200c542000000000000bd9b0200c642000000000000c79b0200c742000000000000d19b0200c842000000000000db9b0200c942000000000000e59b0200ca42000000000000ef9b0200cb42000000000000f99b0200cc42000000000000039c0200cd420000000000000d9c0200ce42000000000000179c0200cf42000000000000219c0200d0420000000000002b9c0200d142000000000000359c0200d2420000000000003f9c0200d342000000000000499c0200d442000000000000539c0200d5420000000000005d9c0200d642000000000000679c0200d742000000000000719c0200d8420000000000007b9c0200d942000000000000859c0200da420000000000008f9c0200db42000000000000999c0200dc42000000000000a39c0200dd42000000000000ad9c0200de42000000000000b79c0200df42000000000000c19c0200e042000000000000cb9c0200e142000000000000d59c0200e242000000000000df9c0200e342000000000000e99c0200e442000000000000f39c0200e542000000000000fd9c0200e642000000000000079d0200e742000000000000119d0200e8420000000000001b9d0200e942000000000000259d0200ea420000000000002f9d0200eb42000000000000399d0200ec42000000000000439d0200ed420000000000004d9d0200ee42000000000000579d0200ef42000000000000619d0200f0420000000000006b9d0200f142000000000000759d0200f2420000000000007f9d0200f342000000000000899d0200f442000000000000939d0200f5420000000000009d9d0200f642000000000000a79d0200f742000000000000b19d0200f842000000000000bb9d0200f942000000000000c59d0200fa42000000000000cf9d0200fb42000000000000d99d0200fc42000000000000e39d0200fd42000000000000ed9d0200fe42000000000000f79d0200ff42000000000000019e020000430000000000000b9e02000143000000000000159e020002430000000000001f9e02000343000000000000299e02000443000000000000339e020005430000000000003d9e02000643000000000000479e02000743000000000000519e020008430000000000005b9e02000943000000000000659e02000a430000000000006f9e02000b43000000000000799e02000c43000000000000839e02000d430000000000008d9e02000e43000000000000979e02000f43000000000000a19e02001043000000000000ab9e02001143000000000000b59e02001243000000000000bf9e02001343000000000000c99e02001443000000000000d39e02001543000000000000dd9e02001643000000000000e79e02001743000000000000f19e02001843000000000000fb9e02001943000000000000059f02001a430000000000000f9f02001b43000000000000199f02001c43000000000000239f02001d430000000000002d9f02001e43000000000000379f02001f43000000000000419f020020430000000000004b9f02002143000000000000559f020022430000000000005f9f02002343000000000000699f02002443000000000000739f020025430000000000007d9f02002643000000000000879f02002743000000000000919f020028430000000000009b9f02002943000000000000a59f02002a43000000000000af9f02002b43000000000000b99f02002c43000000000000c39f02002d43000000000000cd9f02002e43000000000000d79f02002f43000000000000e19f02003043000000000000eb9f02003143000000000000f59f02003243000000000000ff9f0200334300000000000009a00200344300000000000013a0020035430000000000001da00200364300000000000027a00200374300000000000031a0020038430000000000003ba00200394300000000000045a002003a430000000000004fa002003b4300000000000059a002003c4300000000000063a002003d430000000000006da002003e4300000000000077a002003f4300000000000081a0020040430000000000008ba00200414300000000000095a0020042430000000000009fa002004343000000000000a9a002004443000000000000b3a002004543000000000000bda002004643000000000000c7a002004743000000000000d1a002004843000000000000dba002004943000000000000e5a002004a43000000000000efa002004b43000000000000f9a002004c4300000000000003a102004d430000000000000da102004e4300000000000017a102004f4300000000000021a1020050430000000000002ba10200514300000000000035a1020052430000000000003fa10200534300000000000049a10200544300000000000053a1020055430000000000005da10200564300000000000067a10200574300000000000071a1020058430000000000007ba10200594300000000000085a102005a430000000000008fa102005b4300000000000099a102005c43000000000000a3a102005d43000000000000ada102005e43000000000000b7a102005f43000000000000c1a102006043000000000000cba102006143000000000000d5a102006243000000000000dfa102006343000000000000e9a102006443000000000000f3a102006543000000000000fda10200664300000000000007a20200674300000000000011a2020068430000000000001ba20200694300000000000025a202006a430000000000002fa202006b4300000000000039a202006c4300000000000043a202006d430000000000004da202006e4300000000000057a202006f4300000000000061a2020070430000000000006ba20200714300000000000075a2020072430000000000007fa20200734300000000000089a20200744300000000000093a2020075430000000000009da202007643000000000000a7a202007743000000000000b1a202007843000000000000bba202007943000000000000c5a202007a43000000000000cfa202007b43000000000000d9a202007c43000000000000e3a202007d43000000000000eda202007e43000000000000f7a202007f4300000000000001a3020080430000000000000ba30200814300000000000015a3020082430000000000001fa30200834300000000000029a30200844300000000000033a3020085430000000000003da30200864300000000000047a30200874300000000000051a3020088430000000000005ba30200894300000000000065a302008a430000000000006fa302008b4300000000000079a302008c4300000000000083a302008d430000000000008da302008e4300000000000097a302008f43000000000000a1a302009043000000000000aba302009143000000000000b5a302009243000000000000bfa302009343000000000000c9a302009443000000000000d3a302009543000000000000dda302009643000000000000e7a302009743000000000000f1a302009843000000000000fba30200994300000000000005a402009a430000000000000fa402009b4300000000000019a402009c4300000000000023a402009d430000000000002da402009e4300000000000037a402009f4300000000000041a40200a0430000000000004ba40200a14300000000000055a40200a2430000000000005fa40200a34300000000000069a40200a44300000000000073a40200a5430000000000007da40200a64300000000000087a40200a74300000000000091a40200a8430000000000009ba40200a943000000000000a5a40200aa43000000000000afa40200ab43000000000000b9a40200ac43000000000000c3a40200ad43000000000000cda40200ae43000000000000d7a40200af43000000000000e1a40200b043000000000000eba40200b143000000000000f5a40200b243000000000000ffa40200b34300000000000009a50200b44300000000000013a50200b5430000000000001da50200b64300000000000027a50200b74300000000000031a50200b8430000000000003ba50200b94300000000000045a50200ba430000000000004fa50200bb4300000000000059a50200bc4300000000000063a50200bd430000000000006da50200be4300000000000077a50200bf4300000000000081a50200c0430000000000008ba50200c14300000000000095a50200c2430000000000009fa50200c343000000000000a9a50200c443000000000000b3a50200c543000000000000bda50200c643000000000000c7a50200c743000000000000d1a50200c843000000000000dba50200c943000000000000e5a50200ca43000000000000efa50200cb43000000000000f9a50200cc4300000000000003a60200cd430000000000000da60200ce4300000000000017a60200cf4300000000000021a60200d0430000000000002ba60200d14300000000000035a60200d2430000000000003fa60200d34300000000000049a60200d44300000000000053a60200d5430000000000005da60200d64300000000000067a60200d74300000000000071a60200d8430000000000007ba60200d94300000000000085a60200da430000000000008fa60200db4300000000000099a60200dc43000000000000a3a60200dd43000000000000ada60200de43000000000000b7a60200df43000000000000c1a60200e043000000000000cba60200e143000000000000d5a60200e243000000000000dfa60200e343000000000000e9a60200e443000000000000f3a60200e543000000000000fda60200e64300000000000007a70200e74300000000000011a70200e8430000000000001ba70200e94300000000000025a70200ea430000000000002fa70200eb4300000000000039a70200ec4300000000000043a70200ed430000000000004da70200ee4300000000000057a70200ef4300000000000061a70200f0430000000000006ba70200f14300000000000075a70200f2430000000000007fa70200f34300000000000089a70200f44300000000000093a70200f5430000000000009da70200f643000000000000a7a70200f743000000000000b1a70200f843000000000000bba70200f943000000000000c5a70200fa43000000000000cfa70200fb43000000000000d9a70200fc43000000000000e3a70200fd43000000000000eda70200fe43000000000000f7a70200ff4300000000000001a8020000440000000000000ba80200014400000000000015a8020002440000000000001fa80200034400000000000029a80200044400000000000033a8020005440000000000003da80200064400000000000047a80200074400000000000051a8020008440000000000005ba80200094400000000000065a802000a440000000000006fa802000b4400000000000079a802000c4400000000000083a802000d440000000000008da802000e4400000000000097a802000f44000000000000a1a802001044000000000000aba802001144000000000000b5a802001244000000000000bfa802001344000000000000c9a802001444000000000000d3a802001544000000000000dda802001644000000000000e7a802001744000000000000f1a802001844000000000000fba80200194400000000000005a902001a440000000000000fa902001b4400000000000019a902001c4400000000000023a902001d440000000000002da902001e4400000000000037a902001f4400000000000041a9020020440000000000004ba90200214400000000000055a9020022440000000000005fa90200234400000000000069a90200244400000000000073a9020025440000000000007da90200264400000000000087a90200274400000000000091a9020028440000000000009ba902002944000000000000a5a902002a44000000000000afa902002b44000000000000b9a902002c44000000000000c3a902002d44000000000000cda902002e44000000000000d7a902002f44000000000000e1a902003044000000000000eba902003144000000000000f5a902003244000000000000ffa90200334400000000000009aa0200344400000000000013aa020035440000000000001daa0200364400000000000027aa0200374400000000000031aa020038440000000000003baa0200394400000000000045aa02003a440000000000004faa02003b4400000000000059aa02003c4400000000000063aa02003d440000000000006daa02003e4400000000000077aa02003f4400000000000081aa020040440000000000008baa0200414400000000000095aa020042440000000000009faa02004344000000000000a9aa02004444000000000000b3aa02004544000000000000bdaa02004644000000000000c7aa02004744000000000000d1aa02004844000000000000dbaa02004944000000000000e5aa02004a44000000000000efaa02004b44000000000000f9aa02004c4400000000000003ab02004d440000000000000dab02004e4400000000000017ab02004f4400000000000021ab020050440000000000002bab0200514400000000000035ab020052440000000000003fab0200534400000000000049ab0200544400000000000053ab020055440000000000005dab0200564400000000000067ab0200574400000000000071ab020058440000000000007bab0200594400000000000085ab02005a440000000000008fab02005b4400000000000099ab02005c44000000000000a3ab02005d44000000000000adab02005e44000000000000b7ab02005f44000000000000c1ab02006044000000000000cbab02006144000000000000d5ab02006244000000000000dfab02006344000000000000e9ab02006444000000000000f3ab02006544000000000000fdab0200664400000000000007ac0200674400000000000011ac020068440000000000001bac0200694400000000000025ac02006a440000000000002fac02006b4400000000000039ac02006c4400000000000043ac02006d440000000000004dac02006e4400000000000057ac02006f4400000000000061ac020070440000000000006bac0200714400000000000075ac020072440000000000007fac0200734400000000000089ac0200744400000000000093ac020075440000000000009dac02007644000000000000a7ac02007744000000000000b1ac02007844000000000000bbac02007944000000000000c5ac02007a44000000000000cfac02007b44000000000000d9ac02007c44000000000000e3ac02007d44000000000000edac02007e44000000000000f7ac02007f4400000000000001ad020080440000000000000bad0200814400000000000015ad020082440000000000001fad0200834400000000000029ad0200844400000000000033ad020085440000000000003dad0200864400000000000047ad0200874400000000000051ad020088440000000000005bad0200894400000000000065ad02008a440000000000006fad02008b4400000000000079ad02008c4400000000000083ad02008d440000000000008dad02008e4400000000000097ad02008f44000000000000a1ad02009044000000000000abad02009144000000000000b5ad02009244000000000000bfad02009344000000000000c9ad02009444000000000000d3ad02009544000000000000ddad02009644000000000000e7ad02009744000000000000f1ad02009844000000000000fbad0200994400000000000005ae02009a440000000000000fae02009b4400000000000019ae02009c4400000000000023ae02009d440000000000002dae02009e4400000000000037ae02009f4400000000000041ae0200a0440000000000004bae0200a14400000000000055ae0200a2440000000000005fae0200a34400000000000069ae0200a44400000000000073ae0200a5440000000000007dae0200a64400000000000087ae0200a74400000000000091ae0200a8440000000000009bae0200a944000000000000a5ae0200aa44000000000000afae0200ab44000000000000b9ae0200ac44000000000000c3ae0200ad44000000000000cdae0200ae44000000000000d7ae0200af44000000000000e1ae0200b044000000000000ebae0200b144000000000000f5ae0200b244000000000000ffae0200b34400000000000009af0200b44400000000000013af0200b5440000000000001daf0200b64400000000000027af0200b74400000000000031af0200b8440000000000003baf0200b94400000000000045af0200ba440000000000004faf0200bb4400000000000059af0200bc4400000000000063af0200bd440000000000006daf0200be4400000000000077af0200bf4400000000000081af0200c0440000000000008baf0200c14400000000000095af0200c2440000000000009faf0200c344000000000000a9af0200c444000000000000b3af0200c544000000000000bdaf0200c644000000000000c7af0200c744000000000000d1af0200c844000000000000dbaf0200c944000000000000e5af0200ca44000000000000efaf0200cb44000000000000f9af0200cc4400000000000003b00200cd440000000000000db00200ce4400000000000017b00200cf4400000000000021b00200d0440000000000002bb00200d14400000000000035b00200d2440000000000003fb00200d34400000000000049b00200d44400000000000053b00200d5440000000000005db00200d64400000000000067b00200d74400000000000071b00200d8440000000000007bb00200d94400000000000085b00200da440000000000008fb00200db4400000000000099b00200dc44000000000000a3b00200dd44000000000000adb00200de44000000000000b7b00200df44000000000000c1b00200e044000000000000cbb00200e144000000000000d5b00200e244000000000000dfb00200e344000000000000e9b00200e444000000000000f3b00200e544000000000000fdb00200e64400000000000007b10200e74400000000000011b10200e8440000000000001bb10200e94400000000000025b10200ea440000000000002fb10200eb4400000000000039b10200ec4400000000000043b10200ed440000000000004db10200ee4400000000000057b10200ef4400000000000061b10200f0440000000000006bb10200f14400000000000075b10200f2440000000000007fb10200f34400000000000089b10200f44400000000000093b10200f5440000000000009db10200f644000000000000a7b10200f744000000000000b1b10200f844000000000000bbb10200f944000000000000c5b10200fa44000000000000cfb10200fb44000000000000d9b10200fc44000000000000e3b10200fd44000000000000edb10200fe44000000000000f7b10200ff4400000000000001b2020000450000000000000bb20200014500000000000015b2020002450000000000001fb20200034500000000000029b20200044500000000000033b2020005450000000000003db20200064500000000000047b20200074500000000000051b2020008450000000000005bb20200094500000000000065b202000a450000000000006fb202000b4500000000000079b202000c4500000000000083b202000d450000000000008db202000e4500000000000097b202000f45000000000000a1b202001045000000000000abb202001145000000000000b5b202001245000000000000bfb202001345000000000000c9b202001445000000000000d3b202001545000000000000ddb202001645000000000000e7b202001745000000000000f1b202001845000000000000fbb20200194500000000000005b302001a450000000000000fb302001b4500000000000019b302001c4500000000000023b302001d450000000000002db302001e4500000000000037b302001f4500000000000041b3020020450000000000004bb30200214500000000000055b3020022450000000000005fb30200234500000000000069b30200244500000000000073b3020025450000000000007db30200264500000000000087b30200274500000000000091b3020028450000000000009bb302002945000000000000a5b302002a45000000000000afb302002b45000000000000b9b302002c45000000000000c3b302002d45000000000000cdb302002e45000000000000d7b302002f45000000000000e1b302003045000000000000ebb302003145000000000000f5b302003245000000000000ffb30200334500000000000009b40200344500000000000013b4020035450000000000001db40200364500000000000027b40200374500000000000031b4020038450000000000003bb40200394500000000000045b402003a450000000000004fb402003b4500000000000059b402003c4500000000000063b402003d450000000000006db402003e4500000000000077b402003f4500000000000081b4020040450000000000008bb40200414500000000000095b4020042450000000000009fb402004345000000000000a9b402004445000000000000b3b402004545000000000000bdb402004645000000000000c7b402004745000000000000d1b402004845000000000000dbb402004945000000000000e5b402004a45000000000000efb402004b45000000000000f9b402004c4500000000000003b502004d450000000000000db502004e4500000000000017b502004f4500000000000021b5020050450000000000002bb50200514500000000000035b5020052450000000000003fb50200534500000000000049b50200544500000000000053b5020055450000000000005db50200564500000000000067b50200574500000000000071b5020058450000000000007bb50200594500000000000085b502005a450000000000008fb502005b4500000000000099b502005c45000000000000a3b502005d45000000000000adb502005e45000000000000b7b502005f45000000000000c1b502006045000000000000cbb502006145000000000000d5b502006245000000000000dfb502006345000000000000e9b502006445000000000000f3b502006545000000000000fdb50200664500000000000007b60200674500000000000011b6020068450000000000001bb60200694500000000000025b602006a450000000000002fb602006b4500000000000039b602006c4500000000000043b602006d450000000000004db602006e4500000000000057b602006f4500000000000061b6020070450000000000006bb60200714500000000000075b6020072450000000000007fb60200734500000000000089b60200744500000000000093b6020075450000000000009db602007645000000000000a7b602007745000000000000b1b602007845000000000000bbb602007945000000000000c5b602007a45000000000000cfb602007b45000000000000d9b602007c45000000000000e3b602007d45000000000000edb602007e45000000000000f7b602007f4500000000000001b7020080450000000000000bb70200814500000000000015b7020082450000000000001fb70200834500000000000029b70200844500000000000033b7020085450000000000003db70200864500000000000047b70200874500000000000051b7020088450000000000005bb70200894500000000000065b702008a450000000000006fb702008b4500000000000079b702008c4500000000000083b702008d450000000000008db702008e4500000000000097b702008f45000000000000a1b702009045000000000000abb702009145000000000000b5b702009245000000000000bfb702009345000000000000c9b702009445000000000000d3b702009545000000000000ddb702009645000000000000e7b702009745000000000000f1b702009845000000000000fbb70200994500000000000005b802009a450000000000000fb802009b4500000000000019b802009c4500000000000023b802009d450000000000002db802009e4500000000000037b802009f4500000000000041b80200a0450000000000004bb80200a14500000000000055b80200a2450000000000005fb80200a34500000000000069b80200a44500000000000073b80200a5450000000000007db80200a64500000000000087b80200a74500000000000091b80200a8450000000000009bb80200a945000000000000a5b80200aa45000000000000afb80200ab45000000000000b9b80200ac45000000000000c3b80200ad45000000000000cdb80200ae45000000000000d7b80200af45000000000000e1b80200b045000000000000ebb80200b145000000000000f5b80200b245000000000000ffb80200b34500000000000009b90200b44500000000000013b90200b5450000000000001db90200b64500000000000027b90200b74500000000000031b90200b8450000000000003bb90200b94500000000000045b90200ba450000000000004fb90200bb4500000000000059b90200bc4500000000000063b90200bd450000000000006db90200be4500000000000077b90200bf4500000000000081b90200c0450000000000008bb90200c14500000000000095b90200c2450000000000009fb90200c345000000000000a9b90200c445000000000000b3b90200c545000000000000bdb90200c645000000000000c7b90200c745000000000000d1b90200c845000000000000dbb90200c945000000000000e5b90200ca45000000000000efb90200cb45000000000000f9b90200cc4500000000000003ba0200cd450000000000000dba0200ce4500000000000017ba0200cf4500000000000021ba0200d0450000000000002bba0200d14500000000000035ba0200d2450000000000003fba0200d34500000000000049ba0200d44500000000000053ba0200d5450000000000005dba0200d64500000000000067ba0200d74500000000000071ba0200d8450000000000007bba0200d94500000000000085ba0200da450000000000008fba0200db4500000000000099ba0200dc45000000000000a3ba0200dd45000000000000adba0200de45000000000000b7ba0200df45000000000000c1ba0200e045000000000000cbba0200e145000000000000d5ba0200e245000000000000dfba0200e345000000000000e9ba0200e445000000000000f3ba0200e545000000000000fdba0200e64500000000000007bb0200e74500000000000011bb0200e8450000000000001bbb0200e94500000000000025bb0200ea450000000000002fbb0200eb4500000000000039bb0200ec4500000000000043bb0200ed450000000000004dbb0200ee4500000000000057bb0200ef4500000000000061bb0200f0450000000000006bbb0200f14500000000000075bb0200f2450000000000007fbb0200f34500000000000089bb0200f44500000000000093bb0200f5450000000000009dbb0200f645000000000000a7bb0200f745000000000000b1bb0200f845000000000000bbbb0200f945000000000000c5bb0200fa45000000000000cfbb0200fb45000000000000d9bb0200fc45000000000000e3bb0200fd45000000000000edbb0200fe45000000000000f7bb0200ff4500000000000001bc020000460000000000000bbc0200014600000000000015bc020002460000000000001fbc0200034600000000000029bc0200044600000000000033bc020005460000000000003dbc0200064600000000000047bc0200074600000000000051bc020008460000000000005bbc0200094600000000000065bc02000a460000000000006fbc02000b4600000000000079bc02000c4600000000000083bc02000d460000000000008dbc02000e4600000000000097bc02000f46000000000000a1bc02001046000000000000abbc02001146000000000000b5bc02001246000000000000bfbc02001346000000000000c9bc02001446000000000000d3bc02001546000000000000ddbc02001646000000000000e7bc02001746000000000000f1bc02001846000000000000fbbc0200194600000000000005bd02001a460000000000000fbd02001b4600000000000019bd02001c4600000000000023bd02001d460000000000002dbd02001e4600000000000037bd02001f4600000000000041bd020020460000000000004bbd0200214600000000000055bd020022460000000000005fbd0200234600000000000069bd0200244600000000000073bd020025460000000000007dbd0200264600000000000087bd0200274600000000000091bd020028460000000000009bbd02002946000000000000a5bd02002a46000000000000afbd02002b46000000000000b9bd02002c46000000000000c3bd02002d46000000000000cdbd02002e46000000000000d7bd02002f46000000000000e1bd02003046000000000000ebbd02003146000000000000f5bd02003246000000000000ffbd0200334600000000000009be0200344600000000000013be020035460000000000001dbe0200364600000000000027be0200374600000000000031be020038460000000000003bbe0200394600000000000045be02003a460000000000004fbe02003b4600000000000059be02003c4600000000000063be02003d460000000000006dbe02003e4600000000000077be02003f4600000000000081be020040460000000000008bbe0200414600000000000095be020042460000000000009fbe02004346000000000000a9be02004446000000000000b3be02004546000000000000bdbe02004646000000000000c7be02004746000000000000d1be02004846000000000000dbbe02004946000000000000e5be02004a46000000000000efbe02004b46000000000000f9be02004c4600000000000003bf02004d460000000000000dbf02004e4600000000000017bf02004f4600000000000021bf020050460000000000002bbf0200514600000000000035bf020052460000000000003fbf0200534600000000000049bf0200544600000000000053bf020055460000000000005dbf0200564600000000000067bf0200574600000000000071bf020058460000000000007bbf0200594600000000000085bf02005a460000000000008fbf02005b4600000000000099bf02005c46000000000000a3bf02005d46000000000000adbf02005e46000000000000b7bf02005f46000000000000c1bf02006046000000000000cbbf02006146000000000000d5bf02006246000000000000dfbf02006346000000000000e9bf02006446000000000000f3bf02006546000000000000fdbf0200664600000000000007c00200674600000000000011c0020068460000000000001bc00200694600000000000025c002006a460000000000002fc002006b4600000000000039c002006c4600000000000043c002006d460000000000004dc002006e4600000000000057c002006f4600000000000061c0020070460000000000006bc00200714600000000000075c0020072460000000000007fc00200734600000000000089c00200744600000000000093c0020075460000000000009dc002007646000000000000a7c002007746000000000000b1c002007846000000000000bbc002007946000000000000c5c002007a46000000000000cfc002007b46000000000000d9c002007c46000000000000e3c002007d46000000000000edc002007e46000000000000f7c002007f4600000000000001c1020080460000000000000bc10200814600000000000015c1020082460000000000001fc10200834600000000000029c10200844600000000000033c1020085460000000000003dc10200864600000000000047c10200874600000000000051c1020088460000000000005bc10200894600000000000065c102008a460000000000006fc102008b4600000000000079c102008c4600000000000083c102008d460000000000008dc102008e4600000000000097c102008f46000000000000a1c102009046000000000000abc102009146000000000000b5c102009246000000000000bfc102009346000000000000c9c102009446000000000000d3c102009546000000000000ddc102009646000000000000e7c102009746000000000000f1c102009846000000000000fbc10200994600000000000005c202009a460000000000000fc202009b4600000000000019c202009c4600000000000023c202009d460000000000002dc202009e4600000000000037c202009f4600000000000041c20200a0460000000000004bc20200a14600000000000055c20200a2460000000000005fc20200a34600000000000069c20200a44600000000000073c20200a5460000000000007dc20200a64600000000000087c20200a74600000000000091c20200a8460000000000009bc20200a946000000000000a5c20200aa46000000000000afc20200ab46000000000000b9c20200ac46000000000000c3c20200ad46000000000000cdc20200ae46000000000000d7c20200af46000000000000e1c20200b046000000000000ebc20200b146000000000000f5c20200b246000000000000ffc20200b34600000000000009c30200b44600000000000013c30200b5460000000000001dc30200b64600000000000027c30200b74600000000000031c30200b8460000000000003bc30200b94600000000000045c30200ba460000000000004fc30200bb4600000000000059c30200bc4600000000000063c30200bd460000000000006dc30200be4600000000000077c30200bf4600000000000081c30200c0460000000000008bc30200c14600000000000095c30200c2460000000000009fc30200c346000000000000a9c30200c446000000000000b3c30200c546000000000000bdc30200c646000000000000c7c30200c746000000000000d1c30200c846000000000000dbc30200c946000000000000e5c30200ca46000000000000efc30200cb46000000000000f9c30200cc4600000000000003c40200cd460000000000000dc40200ce4600000000000017c40200cf4600000000000021c40200d0460000000000002bc40200d14600000000000035c40200d2460000000000003fc40200d34600000000000049c40200d44600000000000053c40200d5460000000000005dc40200d64600000000000067c40200d74600000000000071c40200d8460000000000007bc40200d94600000000000085c40200da460000000000008fc40200db4600000000000099c40200dc46000000000000a3c40200dd46000000000000adc40200de46000000000000b7c40200df46000000000000c1c40200e046000000000000cbc40200e146000000000000d5c40200e246000000000000dfc40200e346000000000000e9c40200e446000000000000f3c40200e546000000000000fdc40200e64600000000000007c50200e74600000000000011c50200e8460000000000001bc50200e94600000000000025c50200ea460000000000002fc50200eb4600000000000039c50200ec4600000000000043c50200ed460000000000004dc50200ee4600000000000057c50200ef4600000000000061c50200f0460000000000006bc50200f14600000000000075c50200f2460000000000007fc50200f34600000000000089c50200f44600000000000093c50200f5460000000000009dc50200f646000000000000a7c50200f746000000000000b1c50200f846000000000000bbc50200f946000000000000c5c50200fa46000000000000cfc50200fb46000000000000d9c50200fc46000000000000e3c50200fd46000000000000edc50200fe46000000000000f7c50200ff4600000000000001c6020000470000000000000bc60200014700000000000015c6020002470000000000001fc60200034700000000000029c60200044700000000000033c6020005470000000000003dc60200064700000000000047c60200074700000000000051c6020008470000000000005bc60200094700000000000065c602000a470000000000006fc602000b4700000000000079c602000c4700000000000083c602000d470000000000008dc602000e4700000000000097c602000f47000000000000a1c602001047000000000000abc602001147000000000000b5c602001247000000000000bfc602001347000000000000c9c602001447000000000000d3c602001547000000000000ddc602001647000000000000e7c602001747000000000000f1c602001847000000000000fbc60200194700000000000005c702001a470000000000000fc702001b4700000000000019c702001c4700000000000023c702001d470000000000002dc702001e4700000000000037c702001f4700000000000041c7020020470000000000004bc70200214700000000000055c7020022470000000000005fc70200234700000000000069c70200244700000000000073c7020025470000000000007dc70200264700000000000087c70200274700000000000091c7020028470000000000009bc702002947000000000000a5c702002a47000000000000afc702002b47000000000000b9c702002c47000000000000c3c702002d47000000000000cdc702002e47000000000000d7c702002f47000000000000e1c702003047000000000000ebc702003147000000000000f5c702003247000000000000ffc70200334700000000000009c80200344700000000000013c8020035470000000000001dc80200364700000000000027c80200374700000000000031c8020038470000000000003bc80200394700000000000045c802003a470000000000004fc802003b4700000000000059c802003c4700000000000063c802003d470000000000006dc802003e4700000000000077c802003f4700000000000081c8020040470000000000008bc80200414700000000000095c8020042470000000000009fc802004347000000000000a9c802004447000000000000b3c802004547000000000000bdc802004647000000000000c7c802004747000000000000d1c802004847000000000000dbc802004947000000000000e5c802004a47000000000000efc802004b47000000000000f9c802004c4700000000000003c902004d470000000000000dc902004e4700000000000017c902004f4700000000000021c9020050470000000000002bc90200514700000000000035c9020052470000000000003fc90200534700000000000049c90200544700000000000053c9020055470000000000005dc90200564700000000000067c90200574700000000000071c9020058470000000000007bc90200594700000000000085c902005a470000000000008fc902005b4700000000000099c902005c47000000000000a3c902005d47000000000000adc902005e47000000000000b7c902005f47000000000000c1c902006047000000000000cbc902006147000000000000d5c902006247000000000000dfc902006347000000000000e9c902006447000000000000f3c902006547000000000000fdc90200664700000000000007ca0200674700000000000011ca020068470000000000001bca0200694700000000000025ca02006a470000000000002fca02006b4700000000000039ca02006c4700000000000043ca02006d470000000000004dca02006e4700000000000057ca02006f4700000000000061ca020070470000000000006bca0200714700000000000075ca020072470000000000007fca0200734700000000000089ca0200744700000000000093ca020075470000000000009dca02007647000000000000a7ca02007747000000000000b1ca02007847000000000000bbca02007947000000000000c5ca02007a47000000000000cfca02007b47000000000000d9ca02007c47000000000000e3ca02007d47000000000000edca02007e47000000000000f7ca02007f4700000000000001cb020080470000000000000bcb0200814700000000000015cb020082470000000000001fcb0200834700000000000029cb0200844700000000000033cb020085470000000000003dcb0200864700000000000047cb0200874700000000000051cb020088470000000000005bcb0200894700000000000065cb02008a470000000000006fcb02008b4700000000000079cb02008c4700000000000083cb02008d470000000000008dcb02008e4700000000000097cb02008f47000000000000a1cb02009047000000000000abcb02009147000000000000b5cb02009247000000000000bfcb02009347000000000000c9cb02009447000000000000d3cb02009547000000000000ddcb02009647000000000000e7cb02009747000000000000f1cb02009847000000000000fbcb0200994700000000000005cc02009a470000000000000fcc02009b4700000000000019cc02009c4700000000000023cc02009d470000000000002dcc02009e4700000000000037cc02009f4700000000000041cc0200a0470000000000004bcc0200a14700000000000055cc0200a2470000000000005fcc0200a34700000000000069cc0200a44700000000000073cc0200a5470000000000007dcc0200a64700000000000087cc0200a74700000000000091cc0200a8470000000000009bcc0200a947000000000000a5cc0200aa47000000000000afcc0200ab47000000000000b9cc0200ac47000000000000c3cc0200ad47000000000000cdcc0200ae47000000000000d7cc0200af47000000000000e1cc0200b047000000000000ebcc0200b147000000000000f5cc0200b247000000000000ffcc0200b34700000000000009cd0200b44700000000000013cd0200b5470000000000001dcd0200b64700000000000027cd0200b74700000000000031cd0200b8470000000000003bcd0200b94700000000000045cd0200ba470000000000004fcd0200bb4700000000000059cd0200bc4700000000000063cd0200bd470000000000006dcd0200be4700000000000077cd0200bf4700000000000081cd0200c0470000000000008bcd0200c14700000000000095cd0200c2470000000000009fcd0200c347000000000000a9cd0200c447000000000000b3cd0200c547000000000000bdcd0200c647000000000000c7cd0200c747000000000000d1cd0200c847000000000000dbcd0200c947000000000000e5cd0200ca47000000000000efcd0200cb47000000000000f9cd0200cc4700000000000003ce0200cd470000000000000dce0200ce4700000000000017ce0200cf4700000000000021ce0200d0470000000000002bce0200d14700000000000035ce0200d2470000000000003fce0200d34700000000000049ce0200d44700000000000053ce0200d5470000000000005dce0200d64700000000000067ce0200d74700000000000071ce0200d8470000000000007bce0200d94700000000000085ce0200da470000000000008fce0200db4700000000000099ce0200dc47000000000000a3ce0200dd47000000000000adce0200de47000000000000b7ce0200df47000000000000c1ce0200e047000000000000cbce0200e147000000000000d5ce0200e247000000000000dfce0200e347000000000000e9ce0200e447000000000000f3ce0200e547000000000000fdce0200e64700000000000007cf0200e74700000000000011cf0200e8470000000000001bcf0200e94700000000000025cf0200ea470000000000002fcf0200eb4700000000000039cf0200ec4700000000000043cf0200ed470000000000004dcf0200ee4700000000000057cf0200ef4700000000000061cf0200f0470000000000006bcf0200f14700000000000075cf0200f2470000000000007fcf0200f34700000000000089cf0200f44700000000000093cf0200f5470000000000009dcf0200f647000000000000a7cf0200f747000000000000b1cf0200f847000000000000bbcf0200f947000000000000c5cf0200fa47000000000000cfcf0200fb47000000000000d9cf0200fc47000000000000e3cf0200fd47000000000000edcf0200fe47000000000000f7cf0200ff4700000000000001d0020000480000000000000bd00200014800000000000015d0020002480000000000001fd00200034800000000000029d00200044800000000000033d0020005480000000000003dd00200064800000000000047d00200074800000000000051d0020008480000000000005bd00200094800000000000065d002000a480000000000006fd002000b4800000000000079d002000c4800000000000083d002000d480000000000008dd002000e4800000000000097d002000f48000000000000a1d002001048000000000000abd002001148000000000000b5d002001248000000000000bfd002001348000000000000c9d002001448000000000000d3d002001548000000000000ddd002001648000000000000e7d002001748000000000000f1d002001848000000000000fbd00200194800000000000005d102001a480000000000000fd102001b4800000000000019d102001c4800000000000023d102001d480000000000002dd102001e4800000000000037d102001f4800000000000041d1020020480000000000004bd10200214800000000000055d1020022480000000000005fd10200234800000000000069d10200244800000000000073d1020025480000000000007dd10200264800000000000087d10200274800000000000091d1020028480000000000009bd102002948000000000000a5d102002a48000000000000afd102002b48000000000000b9d102002c48000000000000c3d102002d48000000000000cdd102002e48000000000000d7d102002f48000000000000e1d102003048000000000000ebd102003148000000000000f5d102003248000000000000ffd10200334800000000000009d20200344800000000000013d2020035480000000000001dd20200364800000000000027d20200374800000000000031d2020038480000000000003bd20200394800000000000045d202003a480000000000004fd202003b4800000000000059d202003c4800000000000063d202003d480000000000006dd202003e4800000000000077d202003f4800000000000081d2020040480000000000008bd20200414800000000000095d2020042480000000000009fd202004348000000000000a9d202004448000000000000b3d202004548000000000000bdd202004648000000000000c7d202004748000000000000d1d202004848000000000000dbd202004948000000000000e5d202004a48000000000000efd202004b48000000000000f9d202004c4800000000000003d302004d480000000000000dd302004e4800000000000017d302004f4800000000000021d3020050480000000000002bd30200514800000000000035d3020052480000000000003fd30200534800000000000049d30200544800000000000053d3020055480000000000005dd30200564800000000000067d30200574800000000000071d3020058480000000000007bd30200594800000000000085d302005a480000000000008fd302005b4800000000000099d302005c48000000000000a3d302005d48000000000000add302005e48000000000000b7d302005f48000000000000c1d302006048000000000000cbd302006148000000000000d5d302006248000000000000dfd302006348000000000000e9d302006448000000000000f3d302006548000000000000fdd30200664800000000000007d40200674800000000000011d4020068480000000000001bd40200694800000000000025d402006a480000000000002fd402006b4800000000000039d402006c4800000000000043d402006d480000000000004dd402006e4800000000000057d402006f4800000000000061d4020070480000000000006bd40200714800000000000075d4020072480000000000007fd40200734800000000000089d40200744800000000000093d4020075480000000000009dd402007648000000000000a7d402007748000000000000b1d402007848000000000000bbd402007948000000000000c5d402007a48000000000000cfd402007b48000000000000d9d402007c48000000000000e3d402007d48000000000000edd402007e48000000000000f7d402007f4800000000000001d5020080480000000000000bd50200814800000000000015d5020082480000000000001fd50200834800000000000029d50200844800000000000033d5020085480000000000003dd50200864800000000000047d50200874800000000000051d5020088480000000000005bd50200894800000000000065d502008a480000000000006fd502008b4800000000000079d502008c4800000000000083d502008d480000000000008dd502008e4800000000000097d502008f48000000000000a1d502009048000000000000abd502009148000000000000b5d502009248000000000000bfd502009348000000000000c9d502009448000000000000d3d502009548000000000000ddd502009648000000000000e7d502009748000000000000f1d502009848000000000000fbd50200994800000000000005d602009a480000000000000fd602009b4800000000000019d602009c4800000000000023d602009d480000000000002dd602009e4800000000000037d602009f4800000000000041d60200a0480000000000004bd60200a14800000000000055d60200a2480000000000005fd60200a34800000000000069d60200a44800000000000073d60200a5480000000000007dd60200a64800000000000087d60200a74800000000000091d60200a8480000000000009bd60200a948000000000000a5d60200aa48000000000000afd60200ab48000000000000b9d60200ac48000000000000c3d60200ad48000000000000cdd60200ae48000000000000d7d60200af48000000000000e1d60200b048000000000000ebd60200b148000000000000f5d60200b248000000000000ffd60200b34800000000000009d70200b44800000000000013d70200b5480000000000001dd70200b64800000000000027d70200b74800000000000031d70200b8480000000000003bd70200b94800000000000045d70200ba480000000000004fd70200bb4800000000000059d70200bc4800000000000063d70200bd480000000000006dd70200be4800000000000077d70200bf4800000000000081d70200c0480000000000008bd70200c14800000000000095d70200c2480000000000009fd70200c348000000000000a9d70200c448000000000000b3d70200c548000000000000bdd70200c648000000000000c7d70200c748000000000000d1d70200c848000000000000dbd70200c948000000000000e5d70200ca48000000000000efd70200cb48000000000000f9d70200cc4800000000000003d80200cd480000000000000dd80200ce4800000000000017d80200cf4800000000000021d80200d0480000000000002bd80200d14800000000000035d80200d2480000000000003fd80200d34800000000000049d80200d44800000000000053d80200d5480000000000005dd80200d64800000000000067d80200d74800000000000071d80200d8480000000000007bd80200d94800000000000085d80200da480000000000008fd80200db4800000000000099d80200dc48000000000000a3d80200dd48000000000000add80200de48000000000000b7d80200df48000000000000c1d80200e048000000000000cbd80200e148000000000000d5d80200e248000000000000dfd80200e348000000000000e9d80200e448000000000000f3d80200e548000000000000fdd80200e64800000000000007d90200e74800000000000011d90200e8480000000000001bd90200e94800000000000025d90200ea480000000000002fd90200eb4800000000000039d90200ec4800000000000043d90200ed480000000000004dd90200ee4800000000000057d90200ef4800000000000061d90200f0480000000000006bd90200f14800000000000075d90200f2480000000000007fd90200f34800000000000089d90200f44800000000000093d90200f5480000000000009dd90200f648000000000000a7d90200f748000000000000b1d90200f848000000000000bbd90200f948000000000000c5d90200fa48000000000000cfd90200fb48000000000000d9d90200fc48000000000000e3d90200fd48000000000000edd90200fe48000000000000f7d90200ff4800000000000001da020000490000000000000bda0200014900000000000015da020002490000000000001fda0200034900000000000029da0200044900000000000033da020005490000000000003dda0200064900000000000047da0200074900000000000051da020008490000000000005bda0200094900000000000065da02000a490000000000006fda02000b4900000000000079da02000c4900000000000083da02000d490000000000008dda02000e4900000000000097da02000f49000000000000a1da02001049000000000000abda02001149000000000000b5da02001249000000000000bfda02001349000000000000c9da02001449000000000000d3da02001549000000000000ddda02001649000000000000e7da02001749000000000000f1da02001849000000000000fbda0200194900000000000005db02001a490000000000000fdb02001b4900000000000019db02001c4900000000000023db02001d490000000000002ddb02001e4900000000000037db02001f4900000000000041db020020490000000000004bdb0200214900000000000055db020022490000000000005fdb0200234900000000000069db0200244900000000000073db020025490000000000007ddb0200264900000000000087db0200274900000000000091db020028490000000000009bdb02002949000000000000a5db02002a49000000000000afdb02002b49000000000000b9db02002c49000000000000c3db02002d49000000000000cddb02002e49000000000000d7db02002f49000000000000e1db02003049000000000000ebdb02003149000000000000f5db02003249000000000000ffdb0200334900000000000009dc0200344900000000000013dc020035490000000000001ddc0200364900000000000027dc0200374900000000000031dc020038490000000000003bdc0200394900000000000045dc02003a490000000000004fdc02003b4900000000000059dc02003c4900000000000063dc02003d490000000000006ddc02003e4900000000000077dc02003f4900000000000081dc020040490000000000008bdc0200414900000000000095dc020042490000000000009fdc02004349000000000000a9dc02004449000000000000b3dc02004549000000000000bddc02004649000000000000c7dc02004749000000000000d1dc02004849000000000000dbdc02004949000000000000e5dc02004a49000000000000efdc02004b49000000000000f9dc02004c4900000000000003dd02004d490000000000000ddd02004e4900000000000017dd02004f4900000000000021dd020050490000000000002bdd0200514900000000000035dd020052490000000000003fdd0200534900000000000049dd0200544900000000000053dd020055490000000000005ddd0200564900000000000067dd0200574900000000000071dd020058490000000000007bdd0200594900000000000085dd02005a490000000000008fdd02005b4900000000000099dd02005c49000000000000a3dd02005d49000000000000addd02005e49000000000000b7dd02005f49000000000000c1dd02006049000000000000cbdd02006149000000000000d5dd02006249000000000000dfdd02006349000000000000e9dd02006449000000000000f3dd02006549000000000000fddd0200664900000000000007de0200674900000000000011de020068490000000000001bde0200694900000000000025de02006a490000000000002fde02006b4900000000000039de02006c4900000000000043de02006d490000000000004dde02006e4900000000000057de02006f4900000000000061de020070490000000000006bde0200714900000000000075de020072490000000000007fde0200734900000000000089de0200744900000000000093de020075490000000000009dde02007649000000000000a7de02007749000000000000b1de02007849000000000000bbde02007949000000000000c5de02007a49000000000000cfde02007b49000000000000d9de02007c49000000000000e3de02007d49000000000000edde02007e49000000000000f7de02007f4900000000000001df020080490000000000000bdf0200814900000000000015df020082490000000000001fdf0200834900000000000029df0200844900000000000033df020085490000000000003ddf0200864900000000000047df0200874900000000000051df020088490000000000005bdf0200894900000000000065df02008a490000000000006fdf02008b4900000000000079df02008c4900000000000083df02008d490000000000008ddf02008e4900000000000097df02008f49000000000000a1df02009049000000000000abdf02009149000000000000b5df02009249000000000000bfdf02009349000000000000c9df02009449000000000000d3df02009549000000000000dddf02009649000000000000e7df02009749000000000000f1df02009849000000000000fbdf0200994900000000000005e002009a490000000000000fe002009b4900000000000019e002009c4900000000000023e002009d490000000000002de002009e4900000000000037e002009f4900000000000041e00200a0490000000000004be00200a14900000000000055e00200a2490000000000005fe00200a34900000000000069e00200a44900000000000073e00200a5490000000000007de00200a64900000000000087e00200a74900000000000091e00200a8490000000000009be00200a949000000000000a5e00200aa49000000000000afe00200ab49000000000000b9e00200ac49000000000000c3e00200ad49000000000000cde00200ae49000000000000d7e00200af49000000000000e1e00200b049000000000000ebe00200b149000000000000f5e00200b249000000000000ffe00200b34900000000000009e10200b44900000000000013e10200b5490000000000001de10200b64900000000000027e10200b74900000000000031e10200b8490000000000003be10200b94900000000000045e10200ba490000000000004fe10200bb4900000000000059e10200bc4900000000000063e10200bd490000000000006de10200be4900000000000077e10200bf4900000000000081e10200c0490000000000008be10200c14900000000000095e10200c2490000000000009fe10200c349000000000000a9e10200c449000000000000b3e10200c549000000000000bde10200c649000000000000c7e10200c749000000000000d1e10200c849000000000000dbe10200c949000000000000e5e10200ca49000000000000efe10200cb49000000000000f9e10200cc4900000000000003e20200cd490000000000000de20200ce4900000000000017e20200cf4900000000000021e20200d0490000000000002be20200d14900000000000035e20200d2490000000000003fe20200d34900000000000049e20200d44900000000000053e20200d5490000000000005de20200d64900000000000067e20200d74900000000000071e20200d8490000000000007be20200d94900000000000085e20200da490000000000008fe20200db4900000000000099e20200dc49000000000000a3e20200dd49000000000000ade20200de49000000000000b7e20200df49000000000000c1e20200e049000000000000cbe20200e149000000000000d5e20200e249000000000000dfe20200e349000000000000e9e20200e449000000000000f3e20200e549000000000000fde20200e64900000000000007e30200e74900000000000011e30200e8490000000000001be30200e94900000000000025e30200ea490000000000002fe30200eb4900000000000039e30200ec4900000000000043e30200ed490000000000004de30200ee4900000000000057e30200ef4900000000000061e30200f0490000000000006be30200f14900000000000075e30200f2490000000000007fe30200f34900000000000089e30200f44900000000000093e30200f5490000000000009de30200f649000000000000a7e30200f749000000000000b1e30200f849000000000000bbe30200f949000000000000c5e30200fa49000000000000cfe30200fb49000000000000d9e30200fc49000000000000e3e30200fd49000000000000ede30200fe49000000000000f7e30200ff4900000000000001e40200004a0000000000000be40200014a00000000000015e40200024a0000000000001fe40200034a00000000000029e40200044a00000000000033e40200054a0000000000003de40200064a00000000000047e40200074a00000000000051e40200084a0000000000005be40200094a00000000000065e402000a4a0000000000006fe402000b4a00000000000079e402000c4a00000000000083e402000d4a0000000000008de402000e4a00000000000097e402000f4a000000000000a1e40200104a000000000000abe40200114a000000000000b5e40200124a000000000000bfe40200134a000000000000c9e40200144a000000000000d3e40200154a000000000000dde40200164a000000000000e7e40200174a000000000000f1e40200184a000000000000fbe40200194a00000000000005e502001a4a0000000000000fe502001b4a00000000000019e502001c4a00000000000023e502001d4a0000000000002de502001e4a00000000000037e502001f4a00000000000041e50200204a0000000000004be50200214a00000000000055e50200224a0000000000005fe50200234a00000000000069e50200244a00000000000073e50200254a0000000000007de50200264a00000000000087e50200274a00000000000091e50200284a0000000000009be50200294a000000000000a5e502002a4a000000000000afe502002b4a000000000000b9e502002c4a000000000000c3e502002d4a000000000000cde502002e4a000000000000d7e502002f4a000000000000e1e50200304a000000000000ebe50200314a000000000000f5e50200324a000000000000ffe50200334a00000000000009e60200"}} \ No newline at end of file diff --git a/rust/crates/truapi-provider/src/bin/uniffi-bindgen.rs b/rust/crates/truapi-provider/src/bin/uniffi-bindgen.rs new file mode 100644 index 000000000..349e4af38 --- /dev/null +++ b/rust/crates/truapi-provider/src/bin/uniffi-bindgen.rs @@ -0,0 +1,6 @@ +//! Entry point for `uniffi-bindgen` (library mode), used to generate the +//! Swift bindings from the compiled `truapi-provider-ffi` library. + +fn main() { + uniffi::uniffi_bindgen_main() +} diff --git a/rust/crates/truapi-provider/src/config.rs b/rust/crates/truapi-provider/src/config.rs new file mode 100644 index 000000000..2880a3532 --- /dev/null +++ b/rust/crates/truapi-provider/src/config.rs @@ -0,0 +1,120 @@ +//! Per-chain backend configuration. + +/// Where a chain's JSON-RPC service comes from. +/// +/// Registered per genesis hash on a +/// [`EmbeddedChainProviderBuilder`](crate::EmbeddedChainProviderBuilder). +#[derive(Debug, Clone)] +pub enum ChainSource { + /// Remote JSON-RPC node reached over `ws://` or `wss://`. + #[cfg(feature = "ws")] + #[non_exhaustive] + RpcNode { + /// WebSocket endpoint of the node. + url: url::Url, + }, + + /// Embedded smoldot light client. + #[cfg(feature = "smoldot")] + #[non_exhaustive] + LightClient { + /// JSON chain specification of the target chain. + specification: std::borrow::Cow<'static, str>, + /// Warm-start database blob previously returned by the + /// `chainHead_unstable_finalizedDatabase` JSON-RPC function. Invalid + /// blobs are silently ignored by smoldot. + database_content: Option, + /// Whether the statement-store networking protocol is enabled. + statement_protocol: bool, + }, +} + +impl ChainSource { + /// Remote JSON-RPC node backend. + #[cfg(feature = "ws")] + pub fn rpc_node(url: url::Url) -> Self { + ChainSource::RpcNode { url } + } + + /// Start configuring an embedded light-client backend for `specification`; + /// finish with [`LightClientBuilder::build`]. A `ChainSource` is per-chain + /// transport config — a parachain's relay is provider topology, kept apart. + #[cfg(feature = "smoldot")] + pub fn light_client( + specification: impl Into>, + ) -> LightClientBuilder { + LightClientBuilder { + specification: specification.into(), + database_content: None, + } + } +} + +/// Builder for a [`ChainSource::LightClient`]. +/// +/// A chain built here runs the statement-store protocol; whether a bundled +/// chain runs it is set by the catalog, which owns statement-store placement. +#[cfg(feature = "smoldot")] +#[derive(Debug, Clone)] +pub struct LightClientBuilder { + specification: std::borrow::Cow<'static, str>, + database_content: Option, +} + +#[cfg(feature = "smoldot")] +impl LightClientBuilder { + /// Attach a warm-start database blob previously returned by the + /// `chainHead_unstable_finalizedDatabase` JSON-RPC function. Invalid blobs + /// are silently ignored by smoldot. + pub fn database(mut self, database: impl Into) -> Self { + self.database_content = Some(database.into()); + self + } + + /// Finish, producing a [`ChainSource::LightClient`]. + pub fn build(self) -> ChainSource { + ChainSource::LightClient { + specification: self.specification, + database_content: self.database_content, + statement_protocol: true, + } + } +} + +#[cfg(feature = "smoldot")] +impl From for ChainSource { + fn from(builder: LightClientBuilder) -> Self { + builder.build() + } +} + +// Without the `ws` backend `ChainSource` has a single variant, so the let-else +// destructuring below is irrefutable there (e.g. the iOS build). +#[allow(irrefutable_let_patterns)] +#[cfg(all(test, feature = "smoldot"))] +mod tests { + use super::ChainSource; + + #[test] + fn light_client_enables_statement_protocol_by_default() { + let ChainSource::LightClient { + statement_protocol, .. + } = ChainSource::light_client("{}").build() + else { + panic!("expected a LightClient source"); + }; + assert!(statement_protocol); + } + + #[test] + fn builder_sets_database() { + let source = ChainSource::light_client("{}").database("db").build(); + let ChainSource::LightClient { + database_content, .. + } = source + else { + panic!("expected a LightClient source"); + }; + assert_eq!(database_content.as_deref(), Some("db")); + } +} diff --git a/rust/crates/truapi-provider/src/error.rs b/rust/crates/truapi-provider/src/error.rs new file mode 100644 index 000000000..df5461573 --- /dev/null +++ b/rust/crates/truapi-provider/src/error.rs @@ -0,0 +1,203 @@ +//! Typed backend errors and JSON-RPC error synthesis for dropped requests. + +#[cfg(feature = "smoldot")] +use serde_json::{Value, json}; +use truapi::latest::GenericError; + +/// Failure modes surfaced by the provider's backends. +/// +/// Converts to the trait's [`GenericError`] at the +/// [`ChainProvider`](truapi_platform::ChainProvider) boundary while letting +/// in-crate callers match on the cause (e.g. for retry or telemetry). +/// +/// Which variants are constructed depends on the enabled backends and target +/// (e.g. `MissingRuntime` is native-WebSocket only), so the enum as a whole +/// allows dead variants rather than cfg-gating each one. +#[allow(dead_code)] +#[derive(Debug, derive_more::Display)] +pub(crate) enum ProviderError { + /// No backend is registered — and no bundled network defines — this + /// genesis hash. + #[display("no chain registered for genesis 0x{}", hex::encode(genesis))] + UnknownGenesis { + /// The queried genesis hash. + genesis: [u8; 32], + }, + /// A parachain named a relay that is not a registered light-client chain. + #[display( + "relay 0x{} is not a registered light-client chain", + hex::encode(relay) + )] + UnknownRelay { + /// The relay genesis hash the parachain referenced. + relay: [u8; 32], + }, + /// The WebSocket handshake with a remote node failed. + #[display("WebSocket handshake with {url} failed: {reason}")] + Handshake { + /// The node URL. + url: String, + /// The underlying failure. + reason: String, + }, + /// smoldot rejected the chain spec when adding the chain. + #[display("failed to add a chain to the light client: {reason}")] + AddChain { + /// The underlying failure. + reason: String, + }, + /// The native WebSocket backend was called without an ambient tokio + /// runtime to drive its transport. + #[display("the WebSocket backend requires an ambient tokio runtime")] + MissingRuntime, + /// A transport-level failure (e.g. browser WebSocket creation). + #[display("{reason}")] + Transport { + /// The underlying failure. + reason: String, + }, +} + +impl std::error::Error for ProviderError {} + +impl From for GenericError { + fn from(error: ProviderError) -> Self { + GenericError { + reason: error.to_string(), + } + } +} + +/// JSON-RPC internal-error code (per the spec's reserved range). +#[cfg(feature = "smoldot")] +const JSON_RPC_INTERNAL_ERROR: i32 = -32603; + +/// Build a JSON-RPC error response echoing `request`'s id, or `None` when the +/// request carries no id (a notification, which expects no response) or is not +/// valid JSON. +/// +/// The light backend calls this when smoldot refuses a request (a full queue): +/// the connection stays alive, and the consumer correlates responses by id, so +/// without a synthesized error for that id it would wait forever. (The +/// WebSocket backends instead end the whole response stream, since a send +/// failure there means the socket is dead.) +#[cfg(feature = "smoldot")] +pub(crate) fn synthetic_error_frame(request: &str, message: &str) -> Option { + let value: Value = serde_json::from_str(request).ok()?; + let id = value.get("id").filter(|id| !id.is_null())?; + Some( + json!({ + "jsonrpc": "2.0", + "id": id, + "error": { "code": JSON_RPC_INTERNAL_ERROR, "message": message }, + }) + .to_string(), + ) +} + +/// Outcome of matching a JSON-RPC response frame against an awaited request id. +#[cfg(feature = "smoldot")] +pub(crate) enum FrameForId { + /// The frame answers `id` with a string `result`. + Result(String), + /// The frame answers `id` with an `error`, or with a `result` that is not a + /// string. + Failure(String), +} + +/// Match `frame` against the awaited `id`, returning how it answers. +/// +/// `None` means the frame belongs to some other request (or is not a response), +/// so the caller keeps waiting. Both answer cases must terminate the wait: a +/// node that rejects the method answers with an `error` and keeps the socket +/// open, so treating an error frame as unmatched would wait forever. +#[cfg(feature = "smoldot")] +pub(crate) fn frame_for_id(frame: &str, id: &str) -> Option { + let value: Value = serde_json::from_str(frame).ok()?; + if value.get("id")?.as_str()? != id { + return None; + } + if let Some(error) = value.get("error") { + let message = error + .get("message") + .and_then(Value::as_str) + .unwrap_or("unknown error"); + return Some(FrameForId::Failure(message.to_owned())); + } + match value.get("result").and_then(Value::as_str) { + Some(result) => Some(FrameForId::Result(result.to_owned())), + None => Some(FrameForId::Failure( + "response carried no string result".to_owned(), + )), + } +} + +#[cfg(all(test, feature = "smoldot"))] +mod tests { + use super::{FrameForId, frame_for_id, synthetic_error_frame}; + + #[test] + fn a_matching_result_frame_yields_its_string() { + let frame = frame_for_id(r#"{"id":"db","result":"blob"}"#, "db").expect("the id matches"); + let FrameForId::Result(result) = frame else { + panic!("expected a result"); + }; + assert_eq!(result, "blob"); + } + + #[test] + fn a_matching_error_frame_fails_instead_of_waiting() { + // A node that does not implement the method answers with an error and + // keeps the socket open, so this must terminate the caller's wait. + let frame = frame_for_id( + r#"{"id":"db","error":{"code":-32601,"message":"Method not found"}}"#, + "db", + ) + .expect("the id matches"); + let FrameForId::Failure(reason) = frame else { + panic!("expected a failure"); + }; + assert_eq!(reason, "Method not found"); + } + + #[test] + fn a_matching_non_string_result_fails() { + let frame = frame_for_id(r#"{"id":"db","result":null}"#, "db").expect("the id matches"); + assert!(matches!(frame, FrameForId::Failure(_))); + } + + #[test] + fn other_ids_and_notifications_are_not_matched() { + assert!(frame_for_id(r#"{"id":"other","result":"blob"}"#, "db").is_none()); + assert!(frame_for_id(r#"{"method":"chainHead_v1_followEvent"}"#, "db").is_none()); + assert!(frame_for_id("not json", "db").is_none()); + } + + #[test] + fn echoes_the_request_id() { + let frame = synthetic_error_frame( + r#"{"jsonrpc":"2.0","id":7,"method":"x","params":[]}"#, + "queue full", + ) + .expect("a request with an id yields an error frame"); + let value: serde_json::Value = serde_json::from_str(&frame).expect("valid JSON"); + assert_eq!(value["id"], 7); + assert_eq!(value["error"]["code"], -32603); + assert_eq!(value["error"]["message"], "queue full"); + } + + #[test] + fn string_ids_are_preserved() { + let frame = + synthetic_error_frame(r#"{"id":"abc","method":"x"}"#, "boom").expect("has an id"); + let value: serde_json::Value = serde_json::from_str(&frame).expect("valid JSON"); + assert_eq!(value["id"], "abc"); + } + + #[test] + fn notifications_and_garbage_yield_nothing() { + assert!(synthetic_error_frame(r#"{"method":"x","params":[]}"#, "m").is_none()); + assert!(synthetic_error_frame(r#"{"id":null,"method":"x"}"#, "m").is_none()); + assert!(synthetic_error_frame("not json", "m").is_none()); + } +} diff --git a/rust/crates/truapi-provider/src/ffi.rs b/rust/crates/truapi-provider/src/ffi.rs new file mode 100644 index 000000000..a26162861 --- /dev/null +++ b/rust/crates/truapi-provider/src/ffi.rs @@ -0,0 +1,234 @@ +//! Swift/UniFFI bindings (the `uniffi` feature, native targets). +//! +//! Exposes the embedded smoldot [`ChainProvider`](truapi_platform::ChainProvider) +//! to Swift (and other UniFFI targets): build a provider, connect to a chain by +//! genesis hash, and drive the raw JSON-RPC string pipe. Chain specs, relay +//! topology, and statement-store placement come from the bundled network +//! catalog, so the only argument a host supplies is the genesis hash. +//! +//! Inbound responses are delivered to a foreign [`ChainMessageListener`]; a +//! background thread pumps the response stream and invokes it until the +//! connection closes. + +use std::sync::Arc; + +use futures::executor::block_on; +use futures::stream::StreamExt; +use truapi_platform::{ChainProvider as _, JsonRpcConnection}; + +use crate::EmbeddedChainProvider; + +/// Errors surfaced to the foreign caller. +#[derive(Debug, thiserror::Error, uniffi::Error)] +pub enum ChainProviderError { + /// The chain could not be connected (unknown genesis, transport failure). + #[error("{reason}")] + Connect { + /// Human-readable failure reason. + reason: String, + }, + /// The genesis hash was not exactly 32 bytes. + #[error("genesis hash must be 32 bytes")] + BadGenesis, +} + +/// Sink for a connection's inbound JSON-RPC responses and notifications, +/// implemented on the foreign (Swift) side. +#[uniffi::export(with_foreign)] +pub trait ChainMessageListener: Send + Sync { + /// Called for each JSON-RPC response or notification string. + fn on_message(&self, message: String); + /// Called once the connection's response stream ends. + fn on_closed(&self); +} + +/// Embedded-smoldot chain provider. Construct one per process and share it; +/// every connection runs on the single embedded light client. +#[derive(uniffi::Object)] +pub struct ChainProvider { + inner: EmbeddedChainProvider, +} + +#[uniffi::export] +impl ChainProvider { + /// Create a provider backed by the bundled network catalog. + #[uniffi::constructor] + pub fn new() -> Arc { + Arc::new(Self { + inner: EmbeddedChainProvider::builder().build(), + }) + } + + /// Open a connection to the chain identified by `genesis_hash` (32 bytes). + /// The network is resolved from the catalog; responses are delivered to + /// `listener` until the connection closes. + pub fn connect( + &self, + genesis_hash: Vec, + listener: Arc, + ) -> Result, ChainProviderError> { + let genesis: [u8; 32] = genesis_hash + .try_into() + .map_err(|_| ChainProviderError::BadGenesis)?; + let connection = + block_on(self.inner.connect(genesis)).map_err(|error| ChainProviderError::Connect { + reason: error.reason, + })?; + let connection: Arc = Arc::from(connection); + + let mut responses = connection.responses(); + std::thread::spawn(move || { + block_on(async move { + while let Some(message) = responses.next().await { + listener.on_message(message); + } + listener.on_closed(); + }); + }); + + Ok(Arc::new(ChainConnection { inner: connection })) + } +} + +/// A live JSON-RPC connection: a raw string pipe to one chain. +#[derive(uniffi::Object)] +pub struct ChainConnection { + inner: Arc, +} + +#[cfg(all(test, feature = "networks"))] +mod tests { + use std::sync::Mutex; + use std::sync::mpsc::{Receiver, Sender, channel}; + use std::time::Duration; + + use super::*; + + /// Paseo Next v2's relay, whose spec the catalog bundles. Spec-local queries + /// are answered from it without any network access. + const CATALOG_RELAY: [u8; 32] = match const_hex_decode( + "374057be67b355151f271ff70c3db98308c62c8adc48dc6724b6a009a1a014fd", + ) { + Some(bytes) => bytes, + None => panic!("the constant is 32 bytes of hex"), + }; + + /// `hex::decode` is not const, and a literal byte array here would not be + /// greppable against the catalog. + const fn const_hex_decode(hex: &str) -> Option<[u8; 32]> { + let bytes = hex.as_bytes(); + if bytes.len() != 64 { + return None; + } + let mut out = [0u8; 32]; + let mut i = 0; + while i < 32 { + let (high, low) = (nibble(bytes[i * 2]), nibble(bytes[i * 2 + 1])); + match (high, low) { + (Some(high), Some(low)) => out[i] = high << 4 | low, + _ => return None, + } + i += 1; + } + Some(out) + } + + const fn nibble(byte: u8) -> Option { + match byte { + b'0'..=b'9' => Some(byte - b'0'), + b'a'..=b'f' => Some(byte - b'a' + 10), + _ => None, + } + } + + /// Foreign listener stand-in: the real one lives in Swift or Kotlin, so this + /// exercises the same `with_foreign` trait the bindings implement. + struct Collector { + messages: Sender, + closed: Mutex>>, + } + + impl Collector { + fn new() -> (Arc, Receiver, Receiver<()>) { + let (messages, message_rx) = channel(); + let (closed, closed_rx) = channel(); + let collector = Arc::new(Collector { + messages, + closed: Mutex::new(Some(closed)), + }); + (collector, message_rx, closed_rx) + } + } + + impl ChainMessageListener for Collector { + fn on_message(&self, message: String) { + let _ = self.messages.send(message); + } + + fn on_closed(&self) { + if let Some(closed) = self.closed.lock().expect("not poisoned").take() { + let _ = closed.send(()); + } + } + } + + #[test] + fn a_genesis_hash_of_the_wrong_length_is_rejected() { + let (listener, _messages, _closed) = Collector::new(); + let error = ChainProvider::new() + .connect(vec![0u8; 31], listener) + .err() + .expect("a 31-byte genesis must be rejected"); + assert!(matches!(error, ChainProviderError::BadGenesis)); + } + + #[test] + fn a_genesis_hash_outside_the_catalog_is_rejected() { + let (listener, _messages, _closed) = Collector::new(); + let error = ChainProvider::new() + .connect(vec![0xab; 32], listener) + .err() + .expect("an unbundled genesis must be rejected"); + let ChainProviderError::Connect { reason } = error else { + panic!("expected a Connect error"); + }; + assert!(reason.contains(&"ab".repeat(32)), "unexpected: {reason}"); + } + + /// The whole foreign contract end to end: connect by catalog genesis, send a + /// request, receive it on the listener, and see `on_closed` after `close()`. + #[test] + fn a_catalog_chain_answers_on_the_listener_and_closes() { + let (listener, messages, closed) = Collector::new(); + let connection = ChainProvider::new() + .connect(CATALOG_RELAY.to_vec(), listener) + .expect("the catalog resolves its own relay genesis"); + + connection.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_chainName","params":[]}"#.to_owned(), + ); + let response = messages + .recv_timeout(Duration::from_secs(30)) + .expect("smoldot answers spec-local queries without a network"); + assert!(response.contains("Paseo"), "unexpected: {response}"); + + connection.close(); + closed + .recv_timeout(Duration::from_secs(30)) + .expect("closing the connection ends the stream and fires on_closed"); + } +} + +#[uniffi::export] +impl ChainConnection { + /// Queue a JSON-RPC request string. + pub fn send(&self, request: String) { + self.inner.send(request); + } + + /// Close the connection; the listener's `on_closed` fires once the stream + /// ends. + pub fn close(&self) { + self.inner.close(); + } +} diff --git a/rust/crates/truapi-provider/src/js.rs b/rust/crates/truapi-provider/src/js.rs new file mode 100644 index 000000000..3ad7cfad5 --- /dev/null +++ b/rust/crates/truapi-provider/src/js.rs @@ -0,0 +1,266 @@ +//! JavaScript-facing API for browser hosts (`js` feature, wasm32 only). +//! +//! Exposes the provider to JS without a Rust consumer: build a provider from +//! chain registrations, connect per genesis hash, and drive the raw JSON-RPC +//! string pipe. `nextResponse()` is pull-based, mirroring the smoldot npm +//! package's `nextJsonRpcResponse` so existing host code maps 1:1. +//! +//! ```js +//! const builder = new ChainProviderBuilder(); +//! builder.addRpcChain("0x3740…", "wss://node.example"); +//! const provider = builder.build(); +//! const connection = await provider.connect("0x3740…"); +//! connection.send('{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_genesisHash","params":[]}'); +//! const response = await connection.nextResponse(); // undefined once closed +//! connection.close(); +//! ``` +//! +//! Construct one provider per page/worker: connections share the provider's +//! resources, matching the one-provider-per-host-process contract. + +use std::sync::Arc; + +use futures::lock::Mutex; +use futures::stream::{BoxStream, StreamExt}; +use truapi_platform::{ChainProvider as _, JsonRpcConnection}; +use wasm_bindgen::prelude::*; + +use crate::config::ChainSource; +use crate::provider::{EmbeddedChainProvider, EmbeddedChainProviderBuilder}; + +/// Route the embedded provider's (and smoldot's) `tracing` output to the +/// browser console at `level` (`error`|`warn`|`info`|`debug`|`trace`; anything +/// else disables it). Installs the console subscriber on the first call and is +/// safe to call again at any time to retune verbosity. +#[wasm_bindgen(js_name = setLogLevel)] +pub fn set_log_level(level: &str) { + crate::logging::set_level_from_str(level); +} + +/// Collects genesis-hash to chain-source registrations from JS. +#[wasm_bindgen] +pub struct ChainProviderBuilder { + inner: Option, +} + +#[wasm_bindgen] +impl ChainProviderBuilder { + /// Create an empty builder. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + ChainProviderBuilder { + inner: Some(EmbeddedChainProviderBuilder::new()), + } + } + + /// Register a remote JSON-RPC node for the chain identified by the + /// `0x`-prefixed genesis hash. A later registration for the same hash + /// replaces the earlier one. + #[wasm_bindgen(js_name = addRpcChain)] + pub fn add_rpc_chain(&mut self, genesis_hash: &str, url: &str) -> Result<(), JsError> { + let genesis = parse_genesis(genesis_hash)?; + let url = + url::Url::parse(url).map_err(|err| JsError::new(&format!("invalid URL: {err}")))?; + let builder = self + .inner + .take() + .ok_or_else(|| JsError::new("builder was already consumed by build()"))?; + self.inner = Some(builder.chain(genesis, ChainSource::rpc_node(url))); + Ok(()) + } + + /// Register an embedded light-client chain identified by the + /// `0x`-prefixed genesis hash. Use this for a relay or standalone chain; + /// parachains are served through the bundled catalog (`addNetwork`), which + /// supplies their relay wiring and statement-store placement. + #[cfg(feature = "smoldot")] + #[wasm_bindgen(js_name = addLightChain)] + pub fn add_light_chain( + &mut self, + genesis_hash: &str, + specification: String, + ) -> Result<(), JsError> { + let genesis = parse_genesis(genesis_hash)?; + let source = ChainSource::light_client(specification); + let builder = self + .inner + .take() + .ok_or_else(|| JsError::new("builder was already consumed by build()"))?; + self.inner = Some(builder.chain(genesis, source.build())); + Ok(()) + } + + /// Seed a warm-start database blob (from + /// [`snapshot`](ChainProviderHandle::snapshot)) for the `0x`-prefixed + /// genesis hash, so its light client resumes from that finalized state + /// instead of re-syncing from the checkpoint. + #[cfg(feature = "smoldot")] + #[wasm_bindgen(js_name = setDatabase)] + pub fn set_database(&mut self, genesis_hash: &str, blob: String) -> Result<(), JsError> { + let genesis = parse_genesis(genesis_hash)?; + let builder = self + .inner + .take() + .ok_or_else(|| JsError::new("builder was already consumed by build()"))?; + self.inner = Some(builder.database(genesis, blob)); + Ok(()) + } + + /// Register every chain of the bundled network `name` (relay plus system + /// parachains, with relay wiring and statement-store placement supplied by + /// the catalog). Returns the network's genesis hashes. + #[cfg(feature = "networks")] + #[wasm_bindgen(js_name = addNetwork)] + pub fn add_network(&mut self, name: &str) -> Result { + let builder = self + .inner + .take() + .ok_or_else(|| JsError::new("builder was already consumed by build()"))?; + let (builder, chains) = builder + .add_network(name) + .map_err(|err| JsError::new(&err.reason))?; + self.inner = Some(builder); + Ok(NetworkChains { + relay: hex0x(&chains.relay), + assethub: hex0x(&chains.assethub), + bulletin: hex0x(&chains.bulletin), + people: hex0x(&chains.people), + }) + } + + /// Build the provider, consuming the builder. + pub fn build(&mut self) -> Result { + let builder = self + .inner + .take() + .ok_or_else(|| JsError::new("builder was already consumed by build()"))?; + Ok(ChainProviderHandle { + inner: Arc::new(builder.build()), + }) + } +} + +impl Default for ChainProviderBuilder { + /// Same as [`ChainProviderBuilder::new`]: an empty builder. + fn default() -> Self { + Self::new() + } +} + +/// A built provider; hand out one per page/worker. +#[wasm_bindgen] +pub struct ChainProviderHandle { + inner: Arc, +} + +#[wasm_bindgen] +impl ChainProviderHandle { + /// Open a connection to the chain identified by the `0x`-prefixed genesis + /// hash. Rejects when the chain is not registered or the transport fails. + pub async fn connect(&self, genesis_hash: &str) -> Result { + let genesis = parse_genesis(genesis_hash)?; + let connection = self + .inner + .connect(genesis) + .await + .map_err(|err| JsError::new(&err.reason))?; + let responses = connection.responses(); + Ok(Connection { + inner: Arc::from(connection), + responses: Arc::new(Mutex::new(responses)), + }) + } + + /// Produce a warm-start database blob for the `0x`-prefixed genesis hash. + /// Persist it and feed it back via + /// [`setDatabase`](ChainProviderBuilder::set_database) on a later run. + #[cfg(feature = "smoldot")] + pub async fn snapshot(&self, genesis_hash: &str) -> Result { + let genesis = parse_genesis(genesis_hash)?; + self.inner + .snapshot(genesis) + .await + .map_err(|err| JsError::new(&err.reason)) + } +} + +/// A live JSON-RPC connection: a raw string pipe. +#[wasm_bindgen] +pub struct Connection { + inner: Arc, + responses: Arc>>, +} + +#[wasm_bindgen] +impl Connection { + /// Queue a JSON-RPC request string. + pub fn send(&self, request: String) { + self.inner.send(request); + } + + /// Resolve with the next JSON-RPC response or notification, or + /// `undefined` once the connection is closed or dead. + #[wasm_bindgen(js_name = nextResponse)] + pub async fn next_response(&self) -> Option { + let responses = Arc::clone(&self.responses); + let mut responses = responses.lock().await; + responses.next().await + } + + /// Close the connection; pending `nextResponse()` calls resolve to + /// `undefined`. + pub fn close(&self) { + self.inner.close(); + } +} + +/// The genesis hashes of a network registered via +/// [`ChainProviderBuilder::add_network`], as `0x`-prefixed hex strings. +#[cfg(feature = "networks")] +#[wasm_bindgen] +pub struct NetworkChains { + relay: String, + assethub: String, + bulletin: String, + people: String, +} + +#[cfg(feature = "networks")] +#[wasm_bindgen] +impl NetworkChains { + /// Relay-chain genesis hash. + #[wasm_bindgen(getter)] + pub fn relay(&self) -> String { + self.relay.clone() + } + + /// Asset Hub genesis hash. + #[wasm_bindgen(getter)] + pub fn assethub(&self) -> String { + self.assethub.clone() + } + + /// Bulletin-chain genesis hash. + #[wasm_bindgen(getter)] + pub fn bulletin(&self) -> String { + self.bulletin.clone() + } + + /// People-chain genesis hash. + #[wasm_bindgen(getter)] + pub fn people(&self) -> String { + self.people.clone() + } +} + +#[cfg(feature = "networks")] +fn hex0x(bytes: &[u8; 32]) -> String { + format!("0x{}", hex::encode(bytes)) +} + +fn parse_genesis(hex_str: &str) -> Result<[u8; 32], JsError> { + hex::decode(hex_str.trim_start_matches("0x")) + .map_err(|err| JsError::new(&format!("invalid genesis hash hex: {err}")))? + .try_into() + .map_err(|_| JsError::new("genesis hashes are 32 bytes")) +} diff --git a/rust/crates/truapi-provider/src/lib.rs b/rust/crates/truapi-provider/src/lib.rs new file mode 100644 index 000000000..2f90de094 --- /dev/null +++ b/rust/crates/truapi-provider/src/lib.rs @@ -0,0 +1,68 @@ +//! Network provider backends for the [`truapi_platform::ChainProvider`] +//! capability, shared across every host platform. +//! +//! [`EmbeddedChainProvider`] maps chain genesis hashes to a per-chain +//! [`ChainSource`]. Backends hand the caller the raw JSON-RPC string pipe the +//! trait demands; request correlation and subscription routing stay with the +//! consumer (truapi-server's `HostRpcClient`). +//! +//! Per-target backend matrix: +//! +//! - `ws` feature — [`ChainSource::RpcNode`], a remote JSON-RPC node over +//! WebSocket. On native targets it runs on a jsonrpsee transport and needs +//! an ambient tokio runtime; on `wasm32` the same API is served by the +//! browser's `WebSocket`. +//! - `smoldot` feature — [`ChainSource::LightClient`], an embedded +//! [smoldot](https://github.com/paritytech/smoldot) light client. On native +//! targets it runs on smoldot's default platform (OS threads, TCP + plain +//! WebSocket dialing); on `wasm32` it runs on a vendored browser platform +//! (JS event loop, browser `WebSocket` — including `wss` bootnodes). +//! - `networks` feature — a bundled catalog so `connect(genesis_hash)` +//! resolves the whole network (relay wiring + statement placement included) +//! from the genesis hash alone, with no prior registration. +//! - `js` feature — a JavaScript-facing API ([`js`]) on `wasm32`, so web +//! hosts can consume the provider directly without a Rust caller. + +// The `uniffi` feature pulls in UniFFI's generated scaffolding, which contains +// `unsafe` extern-"C" glue; scope the allowance to that feature so every other +// build keeps the crate's no-unsafe guarantee (see `[lints] unsafe_code`). +#![cfg_attr( + all(feature = "uniffi", not(target_arch = "wasm32")), + allow(unsafe_code) +)] + +// The crate is inert without a backend: only the registry would compile, and +// `connect` could never succeed. Fail the build loudly instead. +#[cfg(not(any(feature = "ws", feature = "smoldot")))] +compile_error!("truapi-provider requires at least one backend feature: `ws` or `smoldot`"); + +mod config; +mod error; +#[cfg(all(feature = "uniffi", not(target_arch = "wasm32")))] +mod ffi; +#[cfg(all(feature = "js", target_arch = "wasm32"))] +pub mod js; +#[cfg(feature = "smoldot")] +mod light; +#[cfg(all(feature = "smoldot", target_arch = "wasm32"))] +mod light_platform_web; +#[cfg(all(feature = "js", target_arch = "wasm32"))] +mod logging; +#[cfg(feature = "networks")] +mod networks; +mod provider; +#[cfg(all(feature = "ws", not(target_arch = "wasm32")))] +mod ws; +#[cfg(all(feature = "ws", target_arch = "wasm32"))] +#[path = "ws_web.rs"] +mod ws; + +pub use config::ChainSource; +#[cfg(feature = "smoldot")] +pub use config::LightClientBuilder; +#[cfg(feature = "networks")] +pub use networks::{NetworkChains, known_networks}; +pub use provider::{EmbeddedChainProvider, EmbeddedChainProviderBuilder}; + +#[cfg(all(feature = "uniffi", not(target_arch = "wasm32")))] +uniffi::setup_scaffolding!(); diff --git a/rust/crates/truapi-provider/src/light.rs b/rust/crates/truapi-provider/src/light.rs new file mode 100644 index 000000000..8a659b784 --- /dev/null +++ b/rust/crates/truapi-provider/src/light.rs @@ -0,0 +1,597 @@ +//! Embedded smoldot light-client backend. +//! +//! One smoldot [`Client`] is shared per provider and created lazily on the +//! first light-client connect: the native platform spawns an OS thread pool, +//! while the wasm platform schedules on the JS event loop and dials peers +//! over the browser's `WebSocket`. Each connect adds the chain again: smoldot +//! deduplicates identical chains internally while giving every add its own +//! [`ChainId`], request queue, and response stream, which yields natural +//! per-connection isolation. +//! +//! Warm-start snapshots: take one with +//! [`EmbeddedChainProvider::snapshot`](crate::EmbeddedChainProvider::snapshot), +//! persist the returned string, and feed it back on a later run through +//! [`EmbeddedChainProviderBuilder::database`](crate::EmbeddedChainProviderBuilder::database), +//! which seeds a chain whether it is connected directly or brought up behind +//! one of its parachains. +//! +//! Observability: on native targets smoldot logs through the `log` crate. A +//! host that wants those lines in its `tracing` output should install a +//! `log`->`tracing` bridge (e.g. `tracing_log::LogTracer`); the provider does +//! not install a global logger of its own. + +use core::num::{NonZero, NonZeroUsize}; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::time::Duration; +use std::collections::HashMap; +use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError}; + +use futures::channel::mpsc; +use futures::stream::{self, BoxStream, StreamExt}; +use smoldot_light::{ + AddChainConfig, AddChainConfigJsonRpc, ChainId, Client, HandleRpcError, JsonRpcResponses, + network_service::StatementProtocolConfig, +}; +use truapi_platform::JsonRpcConnection; + +use crate::config::ChainSource; +use crate::error::{ProviderError, synthetic_error_frame}; + +/// Lock a mutex, recovering the guard if a previous holder panicked. +/// +/// The embedded light client is a single process-wide instance shared by every +/// connection, so one poisoning event must not brick all of them. smoldot's own +/// calls under this lock do not panic; this is defense-in-depth for the shared +/// singleton's blast radius. +fn lock(mutex: &Mutex) -> MutexGuard<'_, T> { + mutex.lock().unwrap_or_else(PoisonError::into_inner) +} + +/// The smoldot platform backing this target. +#[cfg(not(target_arch = "wasm32"))] +type Platform = Arc; +#[cfg(target_arch = "wasm32")] +type Platform = crate::light_platform_web::SubxtPlatform; + +fn new_platform() -> Platform { + #[cfg(not(target_arch = "wasm32"))] + { + smoldot_light::platform::DefaultPlatform::new( + env!("CARGO_PKG_NAME").into(), + env!("CARGO_PKG_VERSION").into(), + ) + } + #[cfg(target_arch = "wasm32")] + { + crate::light_platform_web::SubxtPlatform::new() + } +} + +/// Statement-store defaults mirroring smoldot's own example configuration. +const STATEMENT_MAX_SEEN: usize = 65_536; +const STATEMENT_FALSE_POSITIVE_RATE: f64 = 0.01; +const STATEMENT_AFFINITY_UPDATE_INTERVAL: Duration = Duration::from_secs(1); + +/// JSON-RPC queue budgets for chains added by this backend. The provider is a +/// trusted in-process client, so the pending cap is generous (smoldot's docs +/// sanction up to `u32::MAX` for trusted callers); an overflow is still handled +/// gracefully by synthesizing an error response rather than hanging the caller. +const MAX_PENDING_REQUESTS: u32 = 1024; +const MAX_SUBSCRIPTIONS: u32 = 1024; + +struct LightInner { + client: Client, + /// Relay chains added implicitly to sync parachain connections, refcounted + /// by the live parachain connections that named them. A relay is removed + /// from the client when its last such connection closes. + relays: HashMap<[u8; 32], RelayEntry>, +} + +/// A shared implicit relay chain and how many live parachain connections use it. +struct RelayEntry { + chain_id: ChainId, + refcount: usize, +} + +/// Lazily-started shared smoldot client owned by a provider. +pub(crate) struct LightState { + inner: OnceLock>>, +} + +impl LightState { + pub(crate) fn new() -> Self { + LightState { + inner: OnceLock::new(), + } + } + + fn inner(&self) -> &Arc> { + self.inner.get_or_init(|| { + Arc::new(Mutex::new(LightInner { + client: Client::new(new_platform()), + relays: HashMap::new(), + })) + }) + } + + /// Number of implicit relay chains currently held. + #[cfg(test)] + pub(crate) fn relay_count(&self) -> usize { + lock(self.inner()).relays.len() + } + + /// Add `source` to the shared client as a [`JsonRpcConnection`]. For a + /// parachain, `relay` carries its relay's genesis hash and resolved source. + pub(crate) async fn connect( + &self, + source: &ChainSource, + relay: Option<([u8; 32], ChainSource)>, + ) -> Result, ProviderError> { + // `ChainSource` collapses to a single variant when only the smoldot + // backend is enabled (e.g. the iOS build), making this match irrefutable. + #[allow(irrefutable_let_patterns)] + let ChainSource::LightClient { + specification, + database_content, + statement_protocol, + } = source + else { + return Err(ProviderError::Transport { + reason: "light backend invoked with a non-light chain source".to_owned(), + }); + }; + + let inner = Arc::clone(self.inner()); + let mut guard = lock(&inner); + + let relay_genesis = relay.as_ref().map(|(genesis, _)| *genesis); + let relay_id = match &relay { + None => None, + Some((genesis, relay_source)) => Some(add_relay(&mut guard, *genesis, relay_source)?), + }; + + let added = guard + .client + .add_chain(AddChainConfig { + user_data: (), + specification, + database_content: database_content.as_deref().unwrap_or(""), + potential_relay_chains: relay_id.into_iter(), + json_rpc: AddChainConfigJsonRpc::Enabled { + max_pending_requests: NonZero::new(MAX_PENDING_REQUESTS) + .expect("budget is non-zero"), + max_subscriptions: MAX_SUBSCRIPTIONS, + }, + statement_protocol_config: statement_protocol.then(statement_protocol_config), + }) + .map_err(|err| ProviderError::AddChain { + reason: err.to_string(), + }); + + // No connection is constructed on the error path, so nothing would ever + // release the reference taken on the relay above (smoldot rejects the + // add when the spec's relay does not match the one supplied). + let success = match added { + Ok(success) => success, + Err(error) => { + if let Some(genesis) = relay_genesis { + release_relay(&mut guard, genesis); + } + return Err(error); + } + }; + + let responses = success + .json_rpc_responses + .expect("JSON-RPC was enabled for this chain"); + drop(guard); + + // `send` synthesizes an error onto this channel when smoldot rejects a + // request, so a full queue fails the caller fast instead of hanging. + let (errors_tx, errors_rx) = mpsc::unbounded(); + + Ok(Box::new(LightConnection { + inner, + chain_id: success.chain_id, + relay: relay_genesis, + errors_tx, + responses: Mutex::new(Some((responses, errors_rx))), + closed: AtomicBool::new(false), + })) + } +} + +/// Add the relay chain for a parachain entry, reusing an already-added one and +/// taking a reference on it for the calling connection. +/// +/// The relay is added with JSON-RPC disabled: it exists only so the parachain +/// can sync, and a direct connection to the relay genesis goes through its own +/// registry entry. +fn add_relay( + guard: &mut LightInner, + relay_genesis: [u8; 32], + relay_source: &ChainSource, +) -> Result { + if let Some(existing) = guard.relays.get_mut(&relay_genesis) { + existing.refcount += 1; + return Ok(existing.chain_id); + } + + // `ChainSource` collapses to a single variant when only the smoldot backend + // is enabled, making this match irrefutable. + #[allow(irrefutable_let_patterns)] + let ChainSource::LightClient { + specification, + database_content, + statement_protocol, + .. + } = relay_source + else { + return Err(ProviderError::UnknownRelay { + relay: relay_genesis, + }); + }; + + let success = guard + .client + .add_chain(AddChainConfig { + user_data: (), + specification, + database_content: database_content.as_deref().unwrap_or(""), + potential_relay_chains: core::iter::empty(), + json_rpc: AddChainConfigJsonRpc::Disabled, + statement_protocol_config: statement_protocol.then(statement_protocol_config), + }) + .map_err(|err| ProviderError::AddChain { + reason: err.to_string(), + })?; + + guard.relays.insert( + relay_genesis, + RelayEntry { + chain_id: success.chain_id, + refcount: 1, + }, + ); + Ok(success.chain_id) +} + +/// Drop one reference on the implicit relay `relay_genesis`, removing it from +/// the client once the last parachain connection using it is gone. +/// +/// Callers must have already removed (or never added) the parachain chain that +/// held the reference, so no live chain still depends on the relay. +fn release_relay(guard: &mut LightInner, relay_genesis: [u8; 32]) { + let orphaned = match guard.relays.get_mut(&relay_genesis) { + Some(entry) => { + entry.refcount -= 1; + (entry.refcount == 0).then_some(entry.chain_id) + } + None => None, + }; + if let Some(relay_id) = orphaned { + guard.relays.remove(&relay_genesis); + let _: () = guard.client.remove_chain(relay_id); + } +} + +fn statement_protocol_config() -> StatementProtocolConfig { + StatementProtocolConfig::new( + NonZeroUsize::new(STATEMENT_MAX_SEEN).expect("budget is non-zero"), + STATEMENT_FALSE_POSITIVE_RATE, + statement_seed(), + STATEMENT_AFFINITY_UPDATE_INTERVAL, + ) +} + +/// Random bloom-filter seed from the target's entropy source. +fn statement_seed() -> u128 { + #[cfg(not(target_arch = "wasm32"))] + { + rand::random() + } + #[cfg(target_arch = "wasm32")] + { + let mut bytes = [0u8; 16]; + getrandom::getrandom(&mut bytes).expect("the browser provides entropy"); + u128::from_le_bytes(bytes) + } +} + +/// One added smoldot chain exposed as a raw JSON-RPC pipe. +struct LightConnection { + inner: Arc>, + chain_id: ChainId, + /// Genesis of the implicit relay this connection holds a reference on, if + /// it is a parachain; released on close. + relay: Option<[u8; 32]>, + /// Synthetic JSON-RPC error frames for requests smoldot rejected, merged + /// into [`responses`](Self::responses) so the caller fails fast. + errors_tx: mpsc::UnboundedSender, + /// Taken once by `responses()`: smoldot's response stream paired with the + /// receiver for `errors_tx`. + responses: Mutex, mpsc::UnboundedReceiver)>>, + closed: AtomicBool, +} + +impl JsonRpcConnection for LightConnection { + fn send(&self, request: String) { + // The chain-removal check and the request must happen under the same + // lock: json_rpc_request panics on a removed ChainId. + let mut guard = lock(&self.inner); + if self.closed.load(Ordering::SeqCst) { + return; + } + if let Err(HandleRpcError::TooManyPendingRequests { json_rpc_request }) = + guard.client.json_rpc_request(request, self.chain_id) + { + // The connection stays alive (only this request is refused), so + // synthesize an error for its id instead of dropping it silently. + drop(guard); + tracing::warn!("light-client request queue full; failing the request"); + if let Some(frame) = + synthetic_error_frame(&json_rpc_request, "light client request queue full") + { + let _ = self.errors_tx.unbounded_send(frame); + } + } + } + + fn responses(&self) -> BoxStream<'static, String> { + match lock(&self.responses).take() { + Some((responses, errors)) => { + let responses = stream::unfold(responses, |mut responses| async move { + responses.next().await.map(|item| (item, responses)) + }); + stream::select(responses, errors).boxed() + } + None => stream::empty().boxed(), + } + } + + fn close(&self) { + let mut guard = lock(&self.inner); + if self.closed.swap(true, Ordering::SeqCst) { + return; + } + // Removal makes JsonRpcResponses::next() return None; closing the error + // channel ends its half of the merged stream, so `responses()` + // terminates cleanly. + let _: () = guard.client.remove_chain(self.chain_id); + + // This connection's own chain is already removed above, so no live chain + // still depends on the relay it held a reference on. + if let Some(relay_genesis) = self.relay { + release_relay(&mut guard, relay_genesis); + } + + self.errors_tx.close_channel(); + } +} + +impl Drop for LightConnection { + fn drop(&mut self) { + self.close(); + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use futures::executor::block_on; + use futures::stream::StreamExt; + use truapi_platform::ChainProvider; + + use crate::{ChainSource, EmbeddedChainProvider}; + + /// Real relay-chain spec (checkpoint included) vendored from smoldot's + /// demo specs: `add_chain` and spec-local JSON-RPC queries succeed without + /// any network access. + const RELAY_SPEC: &str = include_str!("../tests/fixtures/paseo.json"); + + /// Parachain of [`RELAY_SPEC`], used to exercise the relay-add path. + const PARACHAIN_SPEC: &str = include_str!("../tests/fixtures/paseo_people.json"); + + const RELAY_GENESIS: [u8; 32] = [1; 32]; + const PARACHAIN_GENESIS: [u8; 32] = [2; 32]; + + fn offline_provider() -> EmbeddedChainProvider { + EmbeddedChainProvider::builder() + .chain(RELAY_GENESIS, ChainSource::light_client(RELAY_SPEC).build()) + .build() + } + + #[test] + fn garbage_chain_spec_is_an_error() { + let provider = EmbeddedChainProvider::builder() + .chain( + [1; 32], + ChainSource::light_client("not a chain spec").build(), + ) + .build(); + let error = block_on(provider.connect([1; 32])) + .err() + .expect("a malformed chain spec must fail to connect"); + assert!(error.reason.contains("failed to add a chain")); + } + + #[test] + fn unknown_relay_is_an_error() { + let provider = EmbeddedChainProvider::builder() + .parachain( + RELAY_GENESIS, + ChainSource::light_client(RELAY_SPEC).build(), + [9; 32], + ) + .build(); + let error = block_on(provider.connect(RELAY_GENESIS)) + .err() + .expect("an unregistered relay must fail to connect"); + assert!(error.reason.contains("not a registered light-client chain")); + } + + #[test] + fn chain_name_round_trips_without_a_network() { + let provider = offline_provider(); + let connection = + block_on(provider.connect(RELAY_GENESIS)).expect("offline add_chain succeeds"); + let mut responses = connection.responses(); + connection.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_chainName","params":[]}"#.to_owned(), + ); + let response = block_on(responses.next()).expect("smoldot answers spec-local queries"); + assert!( + response.contains("Paseo Testnet"), + "unexpected response: {response}" + ); + } + + #[test] + fn parachain_reuses_its_registered_relay() { + let provider = EmbeddedChainProvider::builder() + .chain(RELAY_GENESIS, ChainSource::light_client(RELAY_SPEC).build()) + .parachain( + PARACHAIN_GENESIS, + ChainSource::light_client(PARACHAIN_SPEC).build(), + RELAY_GENESIS, + ) + .build(); + // Two connects: the second must reuse the cached relay ChainId. + for _ in 0..2 { + let connection = block_on(provider.connect(PARACHAIN_GENESIS)) + .expect("parachain add_chain succeeds with its relay registered"); + let mut responses = connection.responses(); + connection.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_chainName","params":[]}"# + .to_owned(), + ); + let response = block_on(responses.next()).expect("smoldot answers spec-local queries"); + assert!( + response.contains("Paseo People"), + "unexpected response: {response}" + ); + } + } + + #[test] + fn relay_is_reclaimed_when_the_last_parachain_closes() { + let provider = EmbeddedChainProvider::builder() + .chain(RELAY_GENESIS, ChainSource::light_client(RELAY_SPEC).build()) + .parachain( + PARACHAIN_GENESIS, + ChainSource::light_client(PARACHAIN_SPEC).build(), + RELAY_GENESIS, + ) + .build(); + let first = block_on(provider.connect(PARACHAIN_GENESIS)).expect("parachain connects"); + let second = block_on(provider.connect(PARACHAIN_GENESIS)).expect("parachain connects"); + assert_eq!( + provider.relay_count(), + 1, + "both connections share one relay" + ); + first.close(); + assert_eq!( + provider.relay_count(), + 1, + "the relay stays while a parachain connection is live" + ); + second.close(); + assert_eq!( + provider.relay_count(), + 0, + "the relay is reclaimed after the last parachain connection closes" + ); + } + + #[test] + fn a_failed_parachain_add_releases_its_relay() { + // The relay entry is a chain whose spec id is not the one the parachain + // declares, so smoldot refuses the parachain with NoRelayChainFound + // after the relay has already been added and referenced. + let provider = EmbeddedChainProvider::builder() + .chain( + RELAY_GENESIS, + ChainSource::light_client(PARACHAIN_SPEC).build(), + ) + .parachain( + PARACHAIN_GENESIS, + ChainSource::light_client(PARACHAIN_SPEC).build(), + RELAY_GENESIS, + ) + .build(); + block_on(provider.connect(PARACHAIN_GENESIS)) + .err() + .expect("a parachain whose relay does not match must fail to connect"); + assert_eq!( + provider.relay_count(), + 0, + "the relay must not leak when the parachain add fails" + ); + } + + #[test] + fn close_is_idempotent_and_ends_the_stream() { + let provider = offline_provider(); + let connection = + block_on(provider.connect(RELAY_GENESIS)).expect("offline add_chain succeeds"); + let mut responses = connection.responses(); + connection.close(); + connection.close(); + assert_eq!(block_on(responses.next()), None); + // A late send must not panic on the removed chain. + connection.send( + r#"{"jsonrpc":"2.0","id":2,"method":"chainSpec_v1_chainName","params":[]}"#.to_owned(), + ); + } + + #[test] + fn concurrent_parachain_connects_share_and_reclaim_the_relay() { + // Many threads race the lazy client init and the shared relay refcount; + // once every connection closes the relay must be fully reclaimed, with + // no panic and no leak. + let provider = Arc::new( + EmbeddedChainProvider::builder() + .chain(RELAY_GENESIS, ChainSource::light_client(RELAY_SPEC).build()) + .parachain( + PARACHAIN_GENESIS, + ChainSource::light_client(PARACHAIN_SPEC).build(), + RELAY_GENESIS, + ) + .build(), + ); + let threads: Vec<_> = (0..8) + .map(|_| { + let provider = Arc::clone(&provider); + std::thread::spawn(move || { + let connection = + block_on(provider.connect(PARACHAIN_GENESIS)).expect("parachain connects"); + connection.close(); + }) + }) + .collect(); + for thread in threads { + thread.join().expect("connect thread does not panic"); + } + assert_eq!(provider.relay_count(), 0, "no relay leaks under contention"); + } + + #[test] + fn connections_to_the_same_chain_are_isolated() { + let provider = offline_provider(); + let first = block_on(provider.connect(RELAY_GENESIS)).expect("offline add_chain succeeds"); + let second = block_on(provider.connect(RELAY_GENESIS)).expect("offline add_chain succeeds"); + let mut second_responses = second.responses(); + first.close(); + second.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_chainName","params":[]}"#.to_owned(), + ); + let response = + block_on(second_responses.next()).expect("the second connection stays alive"); + assert!( + response.contains("Paseo Testnet"), + "unexpected response: {response}" + ); + } +} diff --git a/rust/crates/truapi-provider/src/light_platform_web.rs b/rust/crates/truapi-provider/src/light_platform_web.rs new file mode 100644 index 000000000..fcaa03a6e --- /dev/null +++ b/rust/crates/truapi-provider/src/light_platform_web.rs @@ -0,0 +1,13 @@ +// Copyright 2019-2026 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0; see LICENSE-APACHE. + +//! Browser [`smoldot_light::platform::PlatformRef`] implementation, vendored +//! from subxt-lightclient 0.50.1 (`src/platform/wasm_*`, Apache-2.0): tasks +//! spawn on the JS event loop and peers are dialed over the browser's +//! `WebSocket` (which, unlike native sockets, can reach `wss` bootnodes). + +mod helpers; +mod platform; +mod socket; + +pub(crate) use platform::SubxtPlatform; diff --git a/rust/crates/truapi-provider/src/light_platform_web/helpers.rs b/rust/crates/truapi-provider/src/light_platform_web/helpers.rs new file mode 100644 index 000000000..2be05fdc3 --- /dev/null +++ b/rust/crates/truapi-provider/src/light_platform_web/helpers.rs @@ -0,0 +1,42 @@ +// Copyright 2019-2026 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0; see LICENSE-APACHE. +// Vendored from subxt-lightclient 0.50.1 (src/platform/wasm_helpers.rs), used under Apache-2.0. + +//! Wasm implementation for the light client's platform using +//! custom websockets. + +use super::socket::WasmSocket; + +use core::time::Duration; +use futures::{FutureExt, future}; + +pub fn now_from_unix_epoch() -> Duration { + web_time::SystemTime::now() + .duration_since(web_time::SystemTime::UNIX_EPOCH) + .unwrap_or_else(|_| { + panic!("Invalid systime cannot be configured earlier than `UNIX_EPOCH`") + }) +} + +pub type Instant = web_time::Instant; + +pub fn now() -> Instant { + web_time::Instant::now() +} + +pub type Delay = future::BoxFuture<'static, ()>; + +pub fn sleep(duration: Duration) -> Delay { + futures_timer::Delay::new(duration).boxed() +} + +/// Implementation detail of a stream from the `SubxtPlatform`. +#[pin_project::pin_project] +pub struct Stream( + #[pin] + pub smoldot::libp2p::with_buffers::WithBuffers< + future::BoxFuture<'static, Result>, + WasmSocket, + Instant, + >, +); diff --git a/rust/crates/truapi-provider/src/light_platform_web/platform.rs b/rust/crates/truapi-provider/src/light_platform_web/platform.rs new file mode 100644 index 000000000..441139728 --- /dev/null +++ b/rust/crates/truapi-provider/src/light_platform_web/platform.rs @@ -0,0 +1,242 @@ +// Copyright 2019-2026 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0; see LICENSE-APACHE. +// Vendored from subxt-lightclient 0.50.1 (src/platform/wasm_platform.rs), used under Apache-2.0. + +use super::socket::WasmSocket; + +use core::{ + fmt::{self, Write as _}, + net::IpAddr, + time::Duration, +}; +use futures::prelude::*; +use smoldot::libp2p::with_buffers; +use smoldot_light::platform::{ + Address, ConnectionType, LogLevel, MultiStreamAddress, MultiStreamWebRtcConnection, + PlatformRef, SubstreamDirection, +}; + +use std::{io, net::SocketAddr, pin::Pin}; + +const LOG_TARGET: &str = "subxt-platform-wasm"; + +/// Browser [`PlatformRef`] backing the embedded light client: tasks run on the +/// JS event loop and peers are dialed over the browser `WebSocket`. +#[derive(Clone)] +pub struct SubxtPlatform {} + +impl SubxtPlatform { + /// Create a browser platform. Stateless: all per-connection state lives in + /// the sockets it opens. + pub fn new() -> Self { + SubxtPlatform {} + } +} + +impl PlatformRef for SubxtPlatform { + type Delay = super::helpers::Delay; + type Instant = super::helpers::Instant; + type MultiStream = std::convert::Infallible; + type Stream = super::helpers::Stream; + type StreamConnectFuture = future::Ready; + type MultiStreamConnectFuture = future::Pending>; + type ReadWriteAccess<'a> = with_buffers::ReadWriteAccess<'a, Self::Instant>; + type StreamUpdateFuture<'a> = future::BoxFuture<'a, ()>; + type StreamErrorRef<'a> = &'a std::io::Error; + type NextSubstreamFuture<'a> = future::Pending>; + + fn now_from_unix_epoch(&self) -> Duration { + super::helpers::now_from_unix_epoch() + } + + fn now(&self) -> Self::Instant { + super::helpers::now() + } + + fn fill_random_bytes(&self, buffer: &mut [u8]) { + // This could fail if the system does not have access to a good source of entropy. + // Note: `rand::RngCore::fill_bytes` also panics on errors and `rand::OsCore` calls + // identically into `getrandom::getrandom`. + getrandom::getrandom(buffer).expect("Cannot fill random bytes"); + } + + fn sleep(&self, duration: Duration) -> Self::Delay { + super::helpers::sleep(duration) + } + + fn sleep_until(&self, when: Self::Instant) -> Self::Delay { + self.sleep(when.saturating_duration_since(self.now())) + } + + fn spawn_task( + &self, + _task_name: std::borrow::Cow<'_, str>, + task: impl future::Future + Send + 'static, + ) { + wasm_bindgen_futures::spawn_local(task); + } + + fn client_name(&self) -> std::borrow::Cow<'_, str> { + // Report this crate so peer/telemetry attribution names the client + // that is actually connecting, consistent with the native + // `DefaultPlatform` identity. + env!("CARGO_PKG_NAME").into() + } + + fn client_version(&self) -> std::borrow::Cow<'_, str> { + env!("CARGO_PKG_VERSION").into() + } + + fn supports_connection_type(&self, connection_type: ConnectionType) -> bool { + let result = matches!( + connection_type, + ConnectionType::WebSocketIpv4 { .. } + | ConnectionType::WebSocketIpv6 { .. } + | ConnectionType::WebSocketDns { .. } + ); + + tracing::trace!( + target: LOG_TARGET, + "Supports connection type={:?} result={}", + connection_type, result + ); + + result + } + + fn connect_stream(&self, multiaddr: Address) -> Self::StreamConnectFuture { + tracing::trace!(target: LOG_TARGET, "Connect stream to multiaddr={:?}", multiaddr); + + // `PlatformRef` trait guarantees that `connect_stream` is only called with addresses + // stated in `supports_connection_type`. + let addr = match multiaddr { + Address::WebSocketDns { + hostname, + port, + secure: true, + } => { + format!("wss://{hostname}:{port}") + } + Address::WebSocketDns { + hostname, + port, + secure: false, + } => { + format!("ws://{hostname}:{port}") + } + Address::WebSocketIp { + ip: IpAddr::V4(ip), + port, + } => { + let addr = SocketAddr::from((ip, port)); + format!("ws://{addr}") + } + Address::WebSocketIp { + ip: IpAddr::V6(ip), + port, + } => { + let addr = SocketAddr::from((ip, port)); + format!("ws://{addr}") + } + + // The API user of the `PlatformRef` trait is never supposed to open connections of + // a type that isn't supported. + _ => { + unreachable!( + "Connecting to an address not supported. This code path indicates a bug in smoldot. Please raise an issue at https://github.com/smol-dot/smoldot/issues" + ) + } + }; + + let socket_future = async move { + tracing::debug!(target: LOG_TARGET, "Connecting to addr={addr}"); + WasmSocket::new(addr.as_str()).map_err(|err| std::io::Error::other(err.to_string())) + }; + + future::ready(super::helpers::Stream(with_buffers::WithBuffers::new( + Box::pin(socket_future), + ))) + } + + fn connect_multistream(&self, _address: MultiStreamAddress) -> Self::MultiStreamConnectFuture { + panic!( + "Multistreams are not currently supported. This code path indicates a bug in smoldot. Please raise an issue at https://github.com/smol-dot/smoldot/issues" + ) + } + + fn open_out_substream(&self, c: &mut Self::MultiStream) { + // This function can only be called with so-called "multi-stream" connections. We never + // open such connection. + match *c {} + } + + fn next_substream(&self, c: &'_ mut Self::MultiStream) -> Self::NextSubstreamFuture<'_> { + // This function can only be called with so-called "multi-stream" connections. We never + // open such connection. + match *c {} + } + + fn read_write_access<'a>( + &self, + stream: Pin<&'a mut Self::Stream>, + ) -> Result, &'a io::Error> { + let stream = stream.project(); + stream.0.read_write_access(Self::Instant::now()) + } + + fn wait_read_write_again<'a>( + &self, + stream: Pin<&'a mut Self::Stream>, + ) -> Self::StreamUpdateFuture<'a> { + let stream = stream.project(); + Box::pin(stream.0.wait_read_write_again(|when| async move { + let now = super::helpers::now(); + let duration = when.saturating_duration_since(now); + super::helpers::sleep(duration).await; + })) + } + + fn log<'a>( + &self, + log_level: LogLevel, + log_target: &'a str, + message: &'a str, + key_values: impl Iterator, + ) { + // Gate smoldot's lines on the same tracing level as the provider's own + // logs — the identical check tracing's macros make — so a level set via + // `setLogLevel` suppresses smoldot output at the source (before + // formatting), not merely at the subscriber. + let level = match log_level { + LogLevel::Error => tracing::Level::ERROR, + LogLevel::Warn => tracing::Level::WARN, + LogLevel::Info => tracing::Level::INFO, + LogLevel::Debug => tracing::Level::DEBUG, + LogLevel::Trace => tracing::Level::TRACE, + }; + if level > tracing::level_filters::LevelFilter::current() { + return; + } + + let mut message_build = String::with_capacity(128); + message_build.push_str(message); + let mut first = true; + for (key, value) in key_values { + if first { + let _ = write!(message_build, "; "); + first = false; + } else { + let _ = write!(message_build, ", "); + } + let _ = write!(message_build, "{key}={value}"); + } + + match log_level { + LogLevel::Error => tracing::error!("target={log_target} {message_build}"), + LogLevel::Warn => tracing::warn!("target={log_target} {message_build}"), + LogLevel::Info => tracing::info!("target={log_target} {message_build}"), + LogLevel::Debug => tracing::debug!("target={log_target} {message_build}"), + LogLevel::Trace => tracing::trace!("target={log_target} {message_build}"), + }; + } +} diff --git a/rust/crates/truapi-provider/src/light_platform_web/socket.rs b/rust/crates/truapi-provider/src/light_platform_web/socket.rs new file mode 100644 index 000000000..2065f0725 --- /dev/null +++ b/rust/crates/truapi-provider/src/light_platform_web/socket.rs @@ -0,0 +1,254 @@ +// Copyright 2019-2026 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0; see LICENSE-APACHE. +// Vendored from subxt-lightclient 0.50.1 (src/platform/wasm_socket.rs), used +// under Apache-2.0, with one intentional divergence: the `onmessage` handler +// ignores non-`ArrayBuffer` (text) frames instead of panicking (see below). + +use futures::{io, prelude::*}; +use send_wrapper::SendWrapper; +use wasm_bindgen::{JsCast, prelude::*}; + +use std::{ + collections::VecDeque, + pin::Pin, + sync::{Arc, Mutex}, + task::Poll, + task::{Context, Waker}, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Failed to connect {0}")] + ConnectionError(String), +} + +/// Websocket for WASM environments. +/// +/// This is a rust-based wrapper around browser's WebSocket API. +/// +// Warning: It is not safe to have `Clone` on this structure. +pub struct WasmSocket { + /// Inner data shared between `poll` and web_sys callbacks. + inner: Arc>, + /// This implements `Send` and panics if the value is accessed + /// or dropped from another thread. + /// + /// This is safe in wasm environments. + socket: SendWrapper, + /// In memory callbacks to handle messages from the browser socket. + _callbacks: SendWrapper, +} + +/// The state of the [`WasmSocket`]. +#[derive(PartialEq, Eq, Clone, Copy)] +enum ConnectionState { + /// Initial state of the socket. + Connecting, + /// Socket is fully opened. + Opened, + /// Socket is closed. + Closed, + /// Error reported by callbacks. + Error, +} + +struct InnerWasmSocket { + /// The state of the connection. + state: ConnectionState, + /// Data buffer for the socket. + data: VecDeque, + /// Waker from `poll_read` / `poll_write`. + waker: Option, +} + +/// Registered callbacks of the [`WasmSocket`]. +/// +/// These need to be kept around until the socket is dropped. +type Callbacks = ( + Closure, + Closure, + Closure, + Closure, +); + +impl WasmSocket { + /// Establish a WebSocket connection. + /// + /// The error is a string representing the browser error. + /// Visit [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#exceptions_thrown) + /// for more info. + pub fn new(addr: &str) -> Result { + let socket = match web_sys::WebSocket::new(addr) { + Ok(socket) => socket, + Err(err) => return Err(Error::ConnectionError(format!("{err:?}"))), + }; + + socket.set_binary_type(web_sys::BinaryType::Arraybuffer); + + let inner = Arc::new(Mutex::new(InnerWasmSocket { + state: ConnectionState::Connecting, + data: VecDeque::with_capacity(16384), + waker: None, + })); + + let open_callback = Closure::::new({ + let inner = inner.clone(); + move || { + let mut inner = inner.lock().expect("Mutex is poised; qed"); + inner.state = ConnectionState::Opened; + + if let Some(waker) = inner.waker.take() { + waker.wake(); + } + } + }); + socket.set_onopen(Some(open_callback.as_ref().unchecked_ref())); + + let message_callback = Closure::::new({ + let inner = inner.clone(); + move |event: web_sys::MessageEvent| { + // A compliant libp2p peer sends only binary frames. Ignore + // anything else rather than panicking inside a browser callback, + // which would abort the whole light client; the libp2p handshake + // then fails cleanly downstream on the missing bytes. + let Ok(buffer) = event.data().dyn_into::() else { + tracing::warn!("ignoring non-ArrayBuffer WebSocket frame from libp2p peer"); + return; + }; + + let mut inner = inner.lock().expect("Mutex is poised; qed"); + let bytes = js_sys::Uint8Array::new(&buffer).to_vec(); + inner.data.extend(bytes); + + if let Some(waker) = inner.waker.take() { + waker.wake(); + } + } + }); + socket.set_onmessage(Some(message_callback.as_ref().unchecked_ref())); + + let error_callback = Closure::::new({ + let inner = inner.clone(); + move |_event: web_sys::Event| { + // Callback does not provide useful information, signal it back to the stream. + let mut inner = inner.lock().expect("Mutex is poised; qed"); + inner.state = ConnectionState::Error; + + if let Some(waker) = inner.waker.take() { + waker.wake(); + } + } + }); + socket.set_onerror(Some(error_callback.as_ref().unchecked_ref())); + + let close_callback = Closure::::new({ + let inner = inner.clone(); + move |_event: web_sys::CloseEvent| { + let mut inner = inner.lock().expect("Mutex is poised; qed"); + inner.state = ConnectionState::Closed; + + if let Some(waker) = inner.waker.take() { + waker.wake(); + } + } + }); + socket.set_onclose(Some(close_callback.as_ref().unchecked_ref())); + + let callbacks = ( + open_callback, + message_callback, + error_callback, + close_callback, + ); + + Ok(Self { + inner, + socket: SendWrapper::new(socket), + _callbacks: SendWrapper::new(callbacks), + }) + } +} + +impl AsyncRead for WasmSocket { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let mut inner = self.inner.lock().expect("Mutex is poised; qed"); + inner.waker = Some(cx.waker().clone()); + + if self.socket.ready_state() == web_sys::WebSocket::CONNECTING { + return Poll::Pending; + } + + match inner.state { + ConnectionState::Error => Poll::Ready(Err(io::Error::other("Socket error"))), + ConnectionState::Closed => Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())), + ConnectionState::Connecting => Poll::Pending, + ConnectionState::Opened => { + if inner.data.is_empty() { + return Poll::Pending; + } + + let n = inner.data.len().min(buf.len()); + for k in buf.iter_mut().take(n) { + *k = inner.data.pop_front().expect("Buffer non empty; qed"); + } + Poll::Ready(Ok(n)) + } + } + } +} + +impl AsyncWrite for WasmSocket { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let mut inner = self.inner.lock().expect("Mutex is poised; qed"); + inner.waker = Some(cx.waker().clone()); + + match inner.state { + ConnectionState::Error => Poll::Ready(Err(io::Error::other("Socket error"))), + ConnectionState::Closed => Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())), + ConnectionState::Connecting => Poll::Pending, + ConnectionState::Opened => match self.socket.send_with_u8_array(buf) { + Ok(()) => Poll::Ready(Ok(buf.len())), + Err(err) => Poll::Ready(Err(io::Error::other(format!("Write error: {err:?}")))), + }, + } + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.socket.ready_state() == web_sys::WebSocket::CLOSED { + return Poll::Ready(Ok(())); + } + + if self.socket.ready_state() != web_sys::WebSocket::CLOSING { + let _ = self.socket.close(); + } + + let mut inner = self.inner.lock().expect("Mutex is poised; qed"); + inner.waker = Some(cx.waker().clone()); + Poll::Pending + } +} + +impl Drop for WasmSocket { + fn drop(&mut self) { + if self.socket.ready_state() != web_sys::WebSocket::CLOSING { + let _ = self.socket.close(); + } + + self.socket.set_onopen(None); + self.socket.set_onmessage(None); + self.socket.set_onerror(None); + self.socket.set_onclose(None); + } +} diff --git a/rust/crates/truapi-provider/src/logging.rs b/rust/crates/truapi-provider/src/logging.rs new file mode 100644 index 000000000..1875b4634 --- /dev/null +++ b/rust/crates/truapi-provider/src/logging.rs @@ -0,0 +1,205 @@ +//! Level-controlled `tracing` output, routed to the browser console. +//! +//! The provider (and the smoldot light client, whose platform forwards its own +//! log lines through `tracing`) emit via the `tracing` macros and +//! `#[instrument]` spans. A single subscriber installed once by [`init`] routes +//! those events to the matching `console` method, gated by a reloadable +//! [`LevelFilter`] so verbosity is tunable at runtime through [`set_level`] +//! (exposed to JS as `setLogLevel`). Disabled by default ([`LevelFilter::OFF`]). +//! +//! In Chrome, `debug`/`trace` land on `console.debug`, which the DevTools +//! console hides unless its level dropdown includes "Verbose". +//! +//! Output is plaintext, so never log secret material (key bytes, session +//! tokens, signatures). + +use core::fmt::{self, Write as _}; +use std::sync::OnceLock; +use std::sync::atomic::{AtomicBool, Ordering}; + +use tracing::field::{Field, Visit}; +use tracing::span::{Attributes, Record}; +use tracing::{Event, Id, Level, Subscriber}; +use tracing_subscriber::Registry; +use tracing_subscriber::filter::LevelFilter; +use tracing_subscriber::layer::{Context, Layer, SubscriberExt as _}; +use tracing_subscriber::registry::LookupSpan; +use tracing_subscriber::reload; + +static RELOAD_HANDLE: OnceLock> = OnceLock::new(); +static TRACE_SPANS: AtomicBool = AtomicBool::new(false); + +/// Install the global subscriber. Idempotent: the first call wins, later +/// calls (and a foreign subscriber already being set) are no-ops. +pub fn init() { + if RELOAD_HANDLE.get().is_some() { + return; + } + let (filter, handle) = reload::Layer::::new(LevelFilter::OFF); + let subscriber = Registry::default().with(ConsoleLayer.with_filter(filter)); + if tracing::subscriber::set_global_default(subscriber).is_ok() { + let _ = RELOAD_HANDLE.set(handle); + } +} + +/// Set the live verbosity threshold. No-op until [`init`] has run. The reload +/// updates tracing's global max level, which the smoldot platform reads to gate +/// its own lines, so the provider and smoldot share one threshold. +pub fn set_level(level: LevelFilter) { + TRACE_SPANS.store(level == LevelFilter::TRACE, Ordering::Relaxed); + if let Some(handle) = RELOAD_HANDLE.get() { + let _ = handle.reload(level); + } +} + +/// Apply a host-supplied level string, installing the subscriber first so the +/// call works whenever a host chooses to tune it, then emitting a confirmation +/// event so the logging pipeline can be verified end to end. The confirmation +/// is logged at `INFO` (mapping to `console.info`, visible without DevTools +/// "Verbose") rather than at the level just set. +pub fn set_level_from_str(level: &str) { + init(); + set_level(parse_level(level)); + tracing::info!(level, "log level set"); +} + +/// Parse a host-supplied level string. Unknown values disable logging. +pub fn parse_level(level: &str) -> LevelFilter { + match level.to_ascii_lowercase().as_str() { + "error" => LevelFilter::ERROR, + "warn" | "warning" => LevelFilter::WARN, + "info" => LevelFilter::INFO, + "debug" => LevelFilter::DEBUG, + "trace" => LevelFilter::TRACE, + _ => LevelFilter::OFF, + } +} + +/// Routes each event to the console method matching its level. +struct ConsoleLayer; + +impl Layer for ConsoleLayer +where + S: Subscriber, + S: for<'a> LookupSpan<'a>, +{ + fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { + let Some(span) = ctx.span(id) else { + return; + }; + let mut visitor = EventVisitor::default(); + attrs.record(&mut visitor); + span.extensions_mut().insert(SpanFields { + fields: visitor.fields, + }); + if trace_spans_enabled() { + emit_span("new", &span); + } + } + + fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) { + let Some(span) = ctx.span(id) else { + return; + }; + let mut visitor = EventVisitor::default(); + values.record(&mut visitor); + if visitor.fields.is_empty() { + return; + } + let mut extensions = span.extensions_mut(); + if let Some(fields) = extensions.get_mut::() { + if !fields.fields.is_empty() { + fields.fields.push_str(", "); + } + fields.fields.push_str(&visitor.fields); + } else { + extensions.insert(SpanFields { + fields: visitor.fields, + }); + } + } + + fn on_close(&self, id: Id, ctx: Context<'_, S>) { + if !trace_spans_enabled() { + return; + } + let Some(span) = ctx.span(&id) else { + return; + }; + emit_span("close", &span); + } + + fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { + let meta = event.metadata(); + let mut visitor = EventVisitor::default(); + event.record(&mut visitor); + + let mut line = format!("[truapi-provider] {} {}", meta.level(), meta.target()); + if !visitor.message.is_empty() { + let _ = write!(line, ": {}", visitor.message); + } + if !visitor.fields.is_empty() { + let _ = write!(line, " {{{}}}", visitor.fields); + } + emit(*meta.level(), &line); + } +} + +#[derive(Default)] +struct SpanFields { + fields: String, +} + +fn trace_spans_enabled() -> bool { + TRACE_SPANS.load(Ordering::Relaxed) +} + +fn emit_span(kind: &str, span: &tracing_subscriber::registry::SpanRef<'_, S>) +where + S: Subscriber, + S: for<'a> LookupSpan<'a>, +{ + let meta = span.metadata(); + let mut line = format!("[truapi-provider] TRACE {}: span {}", meta.target(), kind); + let extensions = span.extensions(); + let fields = extensions.get::(); + let _ = write!(line, " {{span={:?}", meta.name()); + if let Some(fields) = fields + && !fields.fields.is_empty() + { + let _ = write!(line, ", {}", fields.fields); + } + line.push('}'); + emit(Level::TRACE, &line); +} + +/// Collects the implicit `message` field separately from explicit key-values. +#[derive(Default)] +struct EventVisitor { + message: String, + fields: String, +} + +impl Visit for EventVisitor { + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + if field.name() == "message" { + let _ = write!(self.message, "{value:?}"); + } else { + if !self.fields.is_empty() { + self.fields.push_str(", "); + } + let _ = write!(self.fields, "{}={value:?}", field.name()); + } + } +} + +/// Routes a formatted line to the `console` method matching its level. +fn emit(level: Level, line: &str) { + let js = wasm_bindgen::JsValue::from_str(line); + match level { + Level::ERROR => web_sys::console::error_1(&js), + Level::WARN => web_sys::console::warn_1(&js), + Level::INFO => web_sys::console::info_1(&js), + Level::DEBUG | Level::TRACE => web_sys::console::debug_1(&js), + } +} diff --git a/rust/crates/truapi-provider/src/networks.rs b/rust/crates/truapi-provider/src/networks.rs new file mode 100644 index 000000000..d901f4649 --- /dev/null +++ b/rust/crates/truapi-provider/src/networks.rs @@ -0,0 +1,336 @@ +//! Bundled network catalog. +//! +//! A network is a relay chain plus its system parachains, with the relay +//! topology and statement-store placement fixed here so hosts register a whole +//! network by name instead of assembling specs, relay wiring, and genesis +//! hashes themselves. Only networks that ship a light-sync checkpoint are +//! bundled (a checkpointless spec carries full genesis storage — megabytes +//! unfit for a light-client binary); other networks are supplied per chain via +//! [`ChainSource::light_client`](crate::ChainSource::light_client). + +use std::collections::HashMap; + +use truapi::latest::GenericError; + +use crate::config::ChainSource; +use crate::provider::EmbeddedChainProviderBuilder; + +/// One chain within a [`NetworkDef`]. +struct ChainDef { + /// `0x`-prefixed genesis hash, the chain's stable identity. + genesis_hex: &'static str, + /// Chain-spec JSON. + spec: &'static str, + /// Whether the statement-store networking protocol runs on this chain. + statement_protocol: bool, +} + +/// A relay chain and its system parachains. +struct NetworkDef { + name: &'static str, + relay: ChainDef, + assethub: ChainDef, + bulletin: ChainDef, + people: ChainDef, +} + +/// Genesis hashes of a registered network's chains, returned by +/// [`EmbeddedChainProviderBuilder::add_network`] so the host knows what to +/// [`connect`](crate::EmbeddedChainProvider) to. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct NetworkChains { + /// Relay-chain genesis hash. + pub relay: [u8; 32], + /// Asset Hub genesis hash. + pub assethub: [u8; 32], + /// Bulletin-chain genesis hash. + pub bulletin: [u8; 32], + /// People-chain genesis hash. + pub people: [u8; 32], +} + +const CATALOG: &[NetworkDef] = &[ + NetworkDef { + name: "paseo-next-v2", + relay: ChainDef { + genesis_hex: "0x374057be67b355151f271ff70c3db98308c62c8adc48dc6724b6a009a1a014fd", + spec: include_str!("../networks/paseo.json"), + statement_protocol: false, + }, + assethub: ChainDef { + genesis_hex: "0x23e730eb1c6fecae09c917439a5038cb6122d0d48980e8b9bbf0ff56f94a2ca6", + spec: include_str!("../networks/paseo-next-v2-asset-hub.json"), + statement_protocol: false, + }, + bulletin: ChainDef { + genesis_hex: "0x8cfe6717dc4becfda2e13c488a1e2061ff2dfee96e7d031157f72d36716c0a22", + spec: include_str!("../networks/paseo-next-v2-bulletin.json"), + statement_protocol: false, + }, + people: ChainDef { + genesis_hex: "0x89a63b11fef2c0273fc72c0d864da0793a665dade5db153e0cab995348c5440f", + spec: include_str!("../networks/paseo-next-v2-people.json"), + statement_protocol: true, + }, + }, + NetworkDef { + name: "previewnet", + relay: ChainDef { + genesis_hex: "0x8c27ddf678c2ae9bef0efebfc485a9309f3d735c6d3fbb8d947afc3ace0e80f4", + spec: include_str!("../networks/previewnet.json"), + statement_protocol: false, + }, + assethub: ChainDef { + genesis_hex: "0x4d11c803cc6921429e3876638977ad006ea1bba8cd3976a0bca2f164e7026210", + spec: include_str!("../networks/previewnet-asset-hub.json"), + statement_protocol: false, + }, + bulletin: ChainDef { + genesis_hex: "0x2778b1c94c4362e49a54be57d3056bc714f3712e4486625312704ffb74eb973d", + spec: include_str!("../networks/previewnet-bulletin.json"), + statement_protocol: false, + }, + people: ChainDef { + genesis_hex: "0x3138c6d4ce58c760047a413c2a930e919b4673a841ab4890de59aac3bd037f3d", + spec: include_str!("../networks/previewnet-people.json"), + statement_protocol: true, + }, + }, +]; + +/// Names of the bundled networks. +pub fn known_networks() -> impl Iterator { + CATALOG.iter().map(|network| network.name) +} + +fn genesis(chain: &ChainDef) -> Result<[u8; 32], GenericError> { + hex::decode(chain.genesis_hex.trim_start_matches("0x")) + .ok() + .and_then(|bytes| bytes.try_into().ok()) + .ok_or_else(|| GenericError { + reason: format!("bundled genesis hash {} is malformed", chain.genesis_hex), + }) +} + +/// A network's genesis hashes paired with each chain's genesis hash and +/// [`ChainSource`]. +type NetworkSources = (NetworkChains, Vec<([u8; 32], ChainSource)>); + +/// A resolved catalog network: chains keyed by genesis hash, plus the relay the +/// queried chain syncs through (`None` when it is itself the relay). +type ResolvedNetwork = (HashMap<[u8; 32], ChainSource>, Option<[u8; 32]>); + +/// Genesis hashes and standalone per-chain [`ChainSource`]s of one network; the +/// caller pairs each parachain with the network's relay genesis. +fn network_sources(network: &NetworkDef) -> Result { + let chains = NetworkChains { + relay: genesis(&network.relay)?, + assethub: genesis(&network.assethub)?, + bulletin: genesis(&network.bulletin)?, + people: genesis(&network.people)?, + }; + let sources = vec![ + (chains.relay, light_source(&network.relay)), + (chains.assethub, light_source(&network.assethub)), + (chains.bulletin, light_source(&network.bulletin)), + (chains.people, light_source(&network.people)), + ]; + Ok((chains, sources)) +} + +/// Register every chain of the bundled network `name`, wiring the parachains +/// to the relay and enabling the statement-store protocol where the catalog +/// specifies it. Returns the chains' genesis hashes. +pub(crate) fn add_network( + builder: EmbeddedChainProviderBuilder, + name: &str, +) -> Result<(EmbeddedChainProviderBuilder, NetworkChains), GenericError> { + let network = CATALOG + .iter() + .find(|network| network.name == name) + .ok_or_else(|| GenericError { + reason: format!( + "unknown network \"{name}\"; bundled: {}", + known_networks().collect::>().join(", ") + ), + })?; + + let (chains, sources) = network_sources(network)?; + let relay_genesis = chains.relay; + let builder = sources + .into_iter() + .fold(builder, |builder, (genesis_hash, source)| { + if genesis_hash == relay_genesis { + builder.chain(genesis_hash, source) + } else { + builder.parachain(genesis_hash, source, relay_genesis) + } + }); + Ok((builder, chains)) +} + +/// The network name and service (`relay`/`asset-hub`/`bulletin`/`people`) that +/// `genesis_hash` maps to in the catalog, for log attribution. `None` if no +/// bundled network defines it. +pub(crate) fn catalog_service(genesis_hash: [u8; 32]) -> Option<(&'static str, &'static str)> { + for network in CATALOG { + for (service, chain) in [ + ("relay", &network.relay), + ("asset-hub", &network.assethub), + ("bulletin", &network.bulletin), + ("people", &network.people), + ] { + if genesis(chain).ok() == Some(genesis_hash) { + return Some((network.name, service)); + } + } + } + None +} + +/// Resolve the bundled network containing `genesis_hash` from that hash alone: +/// its chains and the relay `genesis_hash` syncs through. `None` if no bundled +/// network defines it. +pub(crate) fn catalog_network_chains(genesis_hash: [u8; 32]) -> Option { + for network in CATALOG { + let Ok((chains, sources)) = network_sources(network) else { + continue; + }; + if sources.iter().any(|(hash, _)| *hash == genesis_hash) { + // The relay syncs on its own; a parachain syncs through the relay. + let relay = (genesis_hash != chains.relay).then_some(chains.relay); + return Some((sources.into_iter().collect(), relay)); + } + } + None +} + +fn light_source(chain: &ChainDef) -> ChainSource { + ChainSource::LightClient { + specification: chain.spec.into(), + database_content: None, + statement_protocol: chain.statement_protocol, + } +} + +impl EmbeddedChainProviderBuilder { + /// Register every chain of the bundled network `name` (see + /// [`known_networks`]). Returns the builder and the network's genesis + /// hashes. Errors when `name` is not bundled. + pub fn add_network(self, name: &str) -> Result<(Self, NetworkChains), GenericError> { + add_network(self, name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn paseo_next_v2_registers_four_chains_with_expected_hashes() { + let (_, chains) = EmbeddedChainProviderBuilder::new() + .add_network("paseo-next-v2") + .expect("bundled network registers"); + assert_eq!( + hex::encode(chains.relay), + &CATALOG[0].relay.genesis_hex[2..] + ); + assert_eq!( + hex::encode(chains.people), + &CATALOG[0].people.genesis_hex[2..] + ); + } + + /// Every catalog entry's genesis hash must be the hash of the genesis block + /// its own bundled spec describes. + /// + /// A spec refreshed against a chain that has since been re-genesised keeps + /// working for the light client (smoldot derives the real hash from the + /// spec), so the stale entry is invisible until a caller connects by the + /// chain's true genesis hash and is told it is unknown. This derives the + /// hash offline and catches that drift at the refresh. + #[test] + fn every_catalog_genesis_hash_matches_its_bundled_spec() { + for network in CATALOG { + for (service, chain) in [ + ("relay", &network.relay), + ("asset-hub", &network.assethub), + ("bulletin", &network.bulletin), + ("people", &network.people), + ] { + let derived = genesis_hash_of_spec(chain.spec); + assert_eq!( + format!("0x{}", hex::encode(derived)), + chain.genesis_hex, + "the {} {} spec describes a different chain than its catalog entry claims; \ + set its genesis_hex to the left-hand hash", + network.name, + service, + ); + } + } + } + + /// Hash of the genesis block a light chain spec describes. + /// + /// A light spec carries only the genesis state root, which is enough: the + /// genesis block has no parent, number zero, no extrinsics and no digest, + /// so its header is fully determined by that root. + fn genesis_hash_of_spec(spec: &str) -> [u8; 32] { + /// Merkle value of an empty trie, the genesis block's extrinsics root. + const EMPTY_TRIE_ROOT: &str = + "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"; + + let spec: serde_json::Value = serde_json::from_str(spec).expect("bundled spec is JSON"); + let state_root = spec["genesis"]["stateRootHash"] + .as_str() + .expect("bundled spec carries a genesis state root"); + let state_root = + hex::decode(state_root.trim_start_matches("0x")).expect("the state root is hex"); + + let mut header = Vec::with_capacity(98); + header.extend_from_slice(&[0u8; 32]); // parent hash + header.push(0); // block number, SCALE compact 0 + header.extend_from_slice(&state_root); + header.extend_from_slice(&hex::decode(EMPTY_TRIE_ROOT).expect("the constant is hex")); + header.push(0); // digest, an empty vector + + blake2b_simd::Params::new() + .hash_length(32) + .hash(&header) + .as_bytes() + .try_into() + .expect("a 32-byte hash") + } + + #[test] + fn unknown_network_lists_the_catalog() { + let error = EmbeddedChainProviderBuilder::new() + .add_network("mainnet") + .expect_err("an unbundled network must fail"); + assert!(error.reason.contains("paseo-next-v2")); + } + + #[test] + fn connect_resolves_network_from_genesis_alone() { + use futures::executor::block_on; + use futures::stream::StreamExt; + use truapi_platform::ChainProvider; + + // An empty provider — no explicit registration — still connects to a + // catalog chain from its genesis hash alone. + let relay = genesis(&CATALOG[0].relay).expect("catalog genesis parses"); + let provider = crate::EmbeddedChainProvider::builder().build(); + let connection = block_on(provider.connect(relay)) + .expect("catalog resolves the relay genesis without registration"); + let mut responses = connection.responses(); + connection.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_chainName","params":[]}"#.to_owned(), + ); + let response = block_on(responses.next()).expect("smoldot answers spec-local queries"); + assert!( + response.contains("\"Paseo\""), + "unexpected response: {response}" + ); + } +} diff --git a/rust/crates/truapi-provider/src/provider.rs b/rust/crates/truapi-provider/src/provider.rs new file mode 100644 index 000000000..92dae4dc6 --- /dev/null +++ b/rust/crates/truapi-provider/src/provider.rs @@ -0,0 +1,363 @@ +//! Genesis-hash registry dispatching each connection to its backend. +//! +//! A parachain's relay is provider topology (see `relays`), not part of +//! [`ChainSource`]; the light backend brings the relay up behind the parachain. + +use std::collections::HashMap; + +use truapi::latest::GenericError; +use truapi_platform::{ChainProvider, JsonRpcConnection}; + +use crate::config::ChainSource; +use crate::error::ProviderError; + +/// Builder collecting genesis-hash to [`ChainSource`] registrations. +#[derive(Debug, Default)] +pub struct EmbeddedChainProviderBuilder { + chains: HashMap<[u8; 32], ChainSource>, + /// The relay each parachain syncs through, keyed by parachain genesis hash + /// (exactly one per parachain). + #[cfg(feature = "smoldot")] + relays: HashMap<[u8; 32], [u8; 32]>, + /// Warm-start database blobs keyed by genesis hash, applied to a + /// light-client chain at connect time if it has no explicit blob. + #[cfg(feature = "smoldot")] + databases: HashMap<[u8; 32], String>, +} + +impl EmbeddedChainProviderBuilder { + /// Create an empty builder. + pub fn new() -> Self { + Self::default() + } + + /// Register `source` as the backend for the chain identified by + /// `genesis_hash`. A later registration for the same hash replaces the + /// earlier one. + pub fn chain(mut self, genesis_hash: [u8; 32], source: ChainSource) -> Self { + self.chains.insert(genesis_hash, source); + self + } + + /// Register `source` as a parachain syncing through the relay registered + /// under `relay_genesis`. A later registration for `genesis_hash` wins. + #[cfg(feature = "smoldot")] + pub(crate) fn parachain( + mut self, + genesis_hash: [u8; 32], + source: ChainSource, + relay_genesis: [u8; 32], + ) -> Self { + self.chains.insert(genesis_hash, source); + self.relays.insert(genesis_hash, relay_genesis); + self + } + + /// Seed a warm-start database blob (previously produced by + /// [`EmbeddedChainProvider::snapshot`]) for `genesis_hash`, so its light + /// client resumes from that finalized state instead of syncing from the + /// chain-spec checkpoint. Applies to catalog-resolved chains too, and is + /// ignored for a chain that already carries an explicit blob. + #[cfg(feature = "smoldot")] + pub fn database(mut self, genesis_hash: [u8; 32], blob: String) -> Self { + self.databases.insert(genesis_hash, blob); + self + } + + /// Build the provider. Light-client resources start lazily on the first + /// light-client connect. + pub fn build(self) -> EmbeddedChainProvider { + EmbeddedChainProvider { + chains: self.chains, + #[cfg(feature = "smoldot")] + relays: self.relays, + #[cfg(feature = "smoldot")] + databases: self.databases, + #[cfg(feature = "smoldot")] + light: crate::light::LightState::new(), + } + } +} + +/// In-process [`ChainProvider`] whose per-chain backend is a remote WebSocket +/// JSON-RPC node (all targets) or an embedded smoldot light client (native +/// targets). +/// +/// Construct **one provider per host process** and share it (behind an `Arc`) +/// with every consumer: the provider owns the single light-client instance, +/// so host-internal flows (domain resolution, statement store) and product +/// connections share sync, peers, and warm state, while each connection keeps +/// its own isolated JSON-RPC queue and response stream. +/// +/// The `responses()` stream of returned connections is take-once: the first +/// call yields the live stream, later calls yield an ended stream. +pub struct EmbeddedChainProvider { + chains: HashMap<[u8; 32], ChainSource>, + /// The relay each explicitly-registered parachain syncs through; catalog + /// parachains carry theirs in the catalog entry. + #[cfg(feature = "smoldot")] + relays: HashMap<[u8; 32], [u8; 32]>, + #[cfg(feature = "smoldot")] + databases: HashMap<[u8; 32], String>, + #[cfg(feature = "smoldot")] + light: crate::light::LightState, +} + +impl EmbeddedChainProvider { + /// Start building a provider. + pub fn builder() -> EmbeddedChainProviderBuilder { + EmbeddedChainProviderBuilder::new() + } + + /// Open a connection for `source`; for a parachain, `relay`/`chains` give + /// the light backend the relay to sync it through. + /// + /// The relay's source is resolved and warm-start-seeded here, so a blob + /// registered for a relay applies whether it is connected directly or + /// brought up implicitly behind one of its parachains. + #[cfg_attr(not(feature = "smoldot"), allow(unused_variables))] + async fn connect_source( + &self, + source: &ChainSource, + chains: &HashMap<[u8; 32], ChainSource>, + relay: Option<[u8; 32]>, + ) -> Result, ProviderError> { + match source { + #[cfg(feature = "ws")] + ChainSource::RpcNode { url } => crate::ws::connect(url.clone()).await, + #[cfg(feature = "smoldot")] + ChainSource::LightClient { .. } => { + let relay = match relay { + None => None, + Some(relay_genesis) => Some(self.resolve_relay(chains, relay_genesis)?), + }; + self.light.connect(source, relay).await + } + } + } + + /// Resolve a parachain's relay to the source the light backend should add + /// it under, carrying any warm-start blob registered for the relay. + #[cfg(feature = "smoldot")] + fn resolve_relay( + &self, + chains: &HashMap<[u8; 32], ChainSource>, + relay_genesis: [u8; 32], + ) -> Result<([u8; 32], ChainSource), ProviderError> { + let source = chains + .get(&relay_genesis) + .ok_or(ProviderError::UnknownRelay { + relay: relay_genesis, + })?; + Ok(( + relay_genesis, + self.with_seeded_database(relay_genesis, source.clone()), + )) + } + + /// Apply a seeded warm-start database blob to `source` if one exists for + /// `genesis_hash` and the source is a light client with no explicit blob. + #[cfg(feature = "smoldot")] + fn with_seeded_database(&self, genesis_hash: [u8; 32], mut source: ChainSource) -> ChainSource { + if let Some(blob) = self.databases.get(&genesis_hash) + && let ChainSource::LightClient { + database_content, .. + } = &mut source + && database_content.is_none() + { + *database_content = Some(blob.clone()); + } + source + } + + #[cfg(not(feature = "smoldot"))] + fn with_seeded_database(&self, _genesis_hash: [u8; 32], source: ChainSource) -> ChainSource { + source + } +} + +/// Max size for a [`snapshot`](EmbeddedChainProvider::snapshot) database blob. +#[cfg(feature = "smoldot")] +const SNAPSHOT_MAX_BYTES: usize = 8_000_000; + +#[cfg(feature = "smoldot")] +impl EmbeddedChainProvider { + /// Produce a warm-start database blob for `genesis_hash` by asking the + /// embedded light client for its finalized-database snapshot. + /// + /// Persist the returned string and feed it back on a later run via + /// [`EmbeddedChainProviderBuilder::database`] so the chain resumes from + /// finalized state instead of re-syncing from the checkpoint. + /// + /// Meaningful only for light-client chains. A remote node has no local + /// database to snapshot and answers the request with a JSON-RPC error, + /// which surfaces here as an error rather than a wait. + pub async fn snapshot(&self, genesis_hash: [u8; 32]) -> Result { + use futures::stream::StreamExt; + + use crate::error::FrameForId; + + let connection = self.connect(genesis_hash).await?; + let mut responses = connection.responses(); + let id = "truapi-provider:finalizedDatabase"; + connection.send(format!( + concat!( + r#"{{"jsonrpc":"2.0","id":"{}","#, + r#""method":"chainHead_unstable_finalizedDatabase","params":[{}]}}"# + ), + id, SNAPSHOT_MAX_BYTES, + )); + while let Some(frame) = responses.next().await { + match crate::error::frame_for_id(&frame, id) { + Some(FrameForId::Result(result)) => { + connection.close(); + return Ok(result); + } + Some(FrameForId::Failure(reason)) => { + connection.close(); + return Err(ProviderError::Transport { + reason: format!("finalized-database snapshot failed: {reason}"), + } + .into()); + } + None => {} + } + } + connection.close(); + Err(ProviderError::Transport { + reason: "connection ended before the finalized-database snapshot".to_owned(), + } + .into()) + } +} + +#[cfg(all(test, feature = "smoldot"))] +impl EmbeddedChainProvider { + /// Number of implicit relay chains the shared light client currently holds. + pub(crate) fn relay_count(&self) -> usize { + self.light.relay_count() + } +} + +#[truapi_platform::async_trait] +impl ChainProvider for EmbeddedChainProvider { + #[tracing::instrument(skip_all, fields(genesis = %hex::encode(genesis_hash)))] + async fn connect( + &self, + genesis_hash: [u8; 32], + ) -> Result, GenericError> { + // Explicit registrations win; otherwise the catalog resolves the whole + // network from the genesis hash alone. + if let Some(source) = self.chains.get(&genesis_hash) { + let source = self.with_seeded_database(genesis_hash, source.clone()); + #[cfg(feature = "smoldot")] + let relay = self.relays.get(&genesis_hash).copied(); + #[cfg(not(feature = "smoldot"))] + let relay = None; + return Ok(self.connect_source(&source, &self.chains, relay).await?); + } + #[cfg(feature = "networks")] + if let Some((catalog, relay)) = crate::networks::catalog_network_chains(genesis_hash) { + if let Some((network, service)) = crate::networks::catalog_service(genesis_hash) { + tracing::info!(network, service, "connecting via light client"); + } + let source = catalog + .get(&genesis_hash) + .expect("catalog_network_chains includes the queried genesis") + .clone(); + let source = self.with_seeded_database(genesis_hash, source); + return Ok(self.connect_source(&source, &catalog, relay).await?); + } + Err(ProviderError::UnknownGenesis { + genesis: genesis_hash, + } + .into()) + } +} + +#[cfg(test)] +mod tests { + use truapi_platform::ChainProvider; + + use super::EmbeddedChainProvider; + use crate::config::ChainSource; + + #[test] + fn unknown_genesis_is_an_error_naming_the_hash() { + let provider = EmbeddedChainProvider::builder().build(); + let error = futures::executor::block_on(provider.connect([0xab; 32])) + .err() + .expect("connect must fail for an unregistered genesis"); + assert!(error.reason.contains(&"ab".repeat(32))); + } + + /// A blob registered for a relay reaches it even when the relay is never + /// connected directly, only brought up behind one of its parachains. + #[cfg(feature = "smoldot")] + #[test] + // Without the `ws` backend the enum has a single variant, making the + // let-else below irrefutable there. + #[allow(irrefutable_let_patterns)] + fn a_relay_blob_seeds_the_relay_brought_up_behind_a_parachain() { + use std::collections::HashMap; + + const RELAY: [u8; 32] = [1; 32]; + let mut chains = HashMap::new(); + chains.insert(RELAY, ChainSource::light_client("{}").build()); + let provider = EmbeddedChainProvider::builder() + .database(RELAY, "relay-blob".to_owned()) + .build(); + + let (genesis, source) = provider + .resolve_relay(&chains, RELAY) + .expect("the relay is registered"); + assert_eq!(genesis, RELAY); + let ChainSource::LightClient { + database_content, .. + } = source + else { + panic!("expected a LightClient source"); + }; + assert_eq!(database_content.as_deref(), Some("relay-blob")); + } + + /// The snapshot round trip resolves against a live light client, and the + /// blob it returns is accepted back as a warm-start seed. + #[cfg(feature = "smoldot")] + #[test] + fn snapshot_round_trips_into_a_warm_start_seed() { + const GENESIS: [u8; 32] = [1; 32]; + const SPEC: &str = include_str!("../tests/fixtures/paseo.json"); + let provider = EmbeddedChainProvider::builder() + .chain(GENESIS, ChainSource::light_client(SPEC).build()) + .build(); + let blob = futures::executor::block_on(provider.snapshot(GENESIS)) + .expect("the light client answers the finalized-database request"); + assert!(blob.contains("genesisHash"), "unexpected blob: {blob}"); + + let seeded = EmbeddedChainProvider::builder() + .chain(GENESIS, ChainSource::light_client(SPEC).build()) + .database(GENESIS, blob) + .build(); + futures::executor::block_on(seeded.connect(GENESIS)) + .expect("a chain seeded with its own snapshot connects"); + } + + #[cfg(feature = "ws")] + #[test] + // On wasm32 without the smoldot backend the enum has a single variant, + // making the let-else irrefutable there. + #[allow(irrefutable_let_patterns)] + fn later_registration_wins() { + let first = url::Url::parse("ws://first.example").expect("static URL parses"); + let second = url::Url::parse("ws://second.example").expect("static URL parses"); + let provider = EmbeddedChainProvider::builder() + .chain([1; 32], ChainSource::rpc_node(first)) + .chain([1; 32], ChainSource::rpc_node(second.clone())) + .build(); + let ChainSource::RpcNode { url } = &provider.chains[&[1; 32]] else { + panic!("expected an RpcNode source"); + }; + assert_eq!(*url, second); + } +} diff --git a/rust/crates/truapi-provider/src/ws.rs b/rust/crates/truapi-provider/src/ws.rs new file mode 100644 index 000000000..2efda213d --- /dev/null +++ b/rust/crates/truapi-provider/src/ws.rs @@ -0,0 +1,398 @@ +//! Remote WebSocket JSON-RPC backend. +//! +//! The connection is a raw string pipe over a jsonrpsee WebSocket transport: +//! a writer task drains queued requests into the socket and the responses +//! stream yields every inbound text frame untouched. Reconnection is +//! deliberately not handled here — a dropped socket ends the responses +//! stream, and the consumer recovers by connecting again. + +use core::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; + +use futures::channel::mpsc; +use futures::stream::{self, AbortHandle, BoxStream, Stream, StreamExt}; +use jsonrpsee_client_transport::ws::WsTransportClientBuilder; +use jsonrpsee_core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}; +use truapi_platform::JsonRpcConnection; +use url::Url; + +use crate::error::ProviderError; + +/// Bounded depth of the outbound request buffer. The sole producer is a trusted +/// in-process consumer, so this rarely fills; when it does the socket is not +/// draining, and [`send`](WsConnection::send) ends the response stream rather +/// than letting the buffer grow without bound. +const REQUEST_BUFFER: usize = 1024; + +/// Open a WebSocket connection to `url`. +/// +/// Requires an ambient tokio runtime; both the handshake and the spawned +/// writer task run on it. +pub(crate) async fn connect(url: Url) -> Result, ProviderError> { + if tokio::runtime::Handle::try_current().is_err() { + return Err(ProviderError::MissingRuntime); + } + + let (sender, receiver) = WsTransportClientBuilder::default() + .build(url.clone()) + .await + .map_err(|err| ProviderError::Handshake { + url: url.to_string(), + reason: err.to_string(), + })?; + + Ok(Box::new(WsConnection::start(sender, receiver))) +} + +/// A live WebSocket connection exposed as a raw JSON-RPC pipe. +struct WsConnection { + requests: Mutex>, + responses: Mutex>>, + stream_abort: AbortHandle, + closed: AtomicBool, +} + +impl WsConnection { + /// Spawn the writer task and set up the responses stream. + /// + /// Generic over the jsonrpsee transport traits so tests can inject + /// in-memory transports. + fn start(sender: S, receiver: R) -> Self + where + S: TransportSenderT + Send, + R: TransportReceiverT + Send, + { + Self::start_with_buffer(sender, receiver, REQUEST_BUFFER) + } + + /// [`start`](Self::start) with an explicit request-buffer depth, so tests + /// can force an overflow deterministically. + fn start_with_buffer(sender: S, receiver: R, buffer: usize) -> Self + where + S: TransportSenderT + Send, + R: TransportReceiverT + Send, + { + let (requests, request_queue) = mpsc::channel(buffer); + let (responses, stream_abort) = stream::abortable(response_stream(receiver)); + tokio::spawn(writer_pump(sender, request_queue, stream_abort.clone())); + WsConnection { + requests: Mutex::new(requests), + responses: Mutex::new(Some(responses.boxed())), + stream_abort, + closed: AtomicBool::new(false), + } + } +} + +impl JsonRpcConnection for WsConnection { + fn send(&self, request: String) { + if self.closed.load(Ordering::SeqCst) { + return; + } + // A full buffer means the socket is not draining, and a disconnected + // channel means the writer is already gone. Either way, end the + // response stream so the id-correlating consumer reconnects rather than + // buffering without bound or waiting on a request that will not be sent. + if self + .requests + .lock() + .expect("requests mutex poisoned") + .try_send(request) + .is_err() + { + self.close(); + } + } + + fn responses(&self) -> BoxStream<'static, String> { + match self + .responses + .lock() + .expect("responses mutex poisoned") + .take() + { + Some(responses) => responses, + None => stream::empty().boxed(), + } + } + + fn close(&self) { + if self.closed.swap(true, Ordering::SeqCst) { + return; + } + self.requests + .lock() + .expect("requests mutex poisoned") + .close_channel(); + self.stream_abort.abort(); + self.responses + .lock() + .expect("responses mutex poisoned") + .take(); + } +} + +impl Drop for WsConnection { + fn drop(&mut self) { + self.close(); + } +} + +/// Drain queued requests into the transport; on queue close, close the +/// transport gracefully. +/// +/// A send failure means the socket is gone, so it also aborts the response +/// stream: without that, in-flight requests would never be answered and the +/// consumer — which correlates responses by id — would hang. Ending the stream +/// is the disconnect signal it acts on. +async fn writer_pump( + mut sender: S, + mut request_queue: mpsc::Receiver, + stream_abort: AbortHandle, +) { + while let Some(request) = request_queue.next().await { + if let Err(err) = sender.send(request).await { + tracing::warn!("WebSocket send failed: {err}"); + stream_abort.abort(); + return; + } + } + if let Err(err) = sender.close().await { + tracing::debug!("WebSocket close failed: {err}"); + } +} + +/// Yield every inbound text frame; the stream ends on transport error or EOF, +/// which is the disconnect signal consumers rely on. +fn response_stream(receiver: R) -> impl Stream + Send { + stream::unfold(receiver, |mut receiver| async move { + loop { + match receiver.receive().await { + Ok(ReceivedMessage::Text(text)) => return Some((text, receiver)), + Ok(ReceivedMessage::Bytes(bytes)) => match String::from_utf8(bytes) { + Ok(text) => return Some((text, receiver)), + Err(_) => tracing::warn!("dropping non-UTF-8 binary WebSocket frame"), + }, + Ok(ReceivedMessage::Pong) => {} + Err(err) => { + tracing::debug!("WebSocket receive ended: {err}"); + return None; + } + } + } + }) +} + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + + use futures::channel::mpsc; + use futures::stream::StreamExt; + use jsonrpsee_core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}; + use truapi_platform::JsonRpcConnection; + + use super::WsConnection; + + /// Local error type so the fakes need no extra dev-deps. + #[derive(Debug)] + struct FakeError(&'static str); + + impl std::fmt::Display for FakeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.0) + } + } + impl std::error::Error for FakeError {} + + struct FakeSender { + sent: mpsc::UnboundedSender, + closed: std::sync::Arc>, + } + + #[truapi_platform::async_trait] + impl TransportSenderT for FakeSender { + type Error = FakeError; + + async fn send(&mut self, msg: String) -> Result<(), Self::Error> { + self.sent + .unbounded_send(msg) + .map_err(|_| FakeError("sink gone")) + } + + async fn close(&mut self) -> Result<(), Self::Error> { + *self.closed.lock().expect("lock") = true; + Ok(()) + } + } + + /// Sender whose `send` always fails, modelling a dead socket. + struct FailingSender; + + #[truapi_platform::async_trait] + impl TransportSenderT for FailingSender { + type Error = FakeError; + + async fn send(&mut self, _msg: String) -> Result<(), Self::Error> { + Err(FakeError("dead socket")) + } + + async fn close(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + } + + /// Sender whose `send` never resolves, so the writer parks after taking one + /// request and the bounded buffer fills. + struct StalledSender; + + #[truapi_platform::async_trait] + impl TransportSenderT for StalledSender { + type Error = FakeError; + + async fn send(&mut self, _msg: String) -> Result<(), Self::Error> { + core::future::pending().await + } + + async fn close(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + } + + struct FakeReceiver { + frames: mpsc::UnboundedReceiver>, + } + + #[truapi_platform::async_trait] + impl TransportReceiverT for FakeReceiver { + type Error = FakeError; + + async fn receive(&mut self) -> Result { + match self.frames.next().await { + Some(frame) => frame, + None => Err(FakeError("eof")), + } + } + } + + struct Harness { + connection: WsConnection, + sent: mpsc::UnboundedReceiver, + frames: mpsc::UnboundedSender>, + sender_closed: std::sync::Arc>, + } + + fn harness() -> Harness { + let (sent_tx, sent_rx) = mpsc::unbounded(); + let (frames_tx, frames_rx) = mpsc::unbounded(); + let sender_closed = std::sync::Arc::new(Mutex::new(false)); + let connection = WsConnection::start( + FakeSender { + sent: sent_tx, + closed: std::sync::Arc::clone(&sender_closed), + }, + FakeReceiver { frames: frames_rx }, + ); + Harness { + connection, + sent: sent_rx, + frames: frames_tx, + sender_closed, + } + } + + #[tokio::test] + async fn requests_reach_the_transport() { + let mut harness = harness(); + harness.connection.send("one".to_owned()); + harness.connection.send("two".to_owned()); + assert_eq!(harness.sent.next().await.as_deref(), Some("one")); + assert_eq!(harness.sent.next().await.as_deref(), Some("two")); + } + + #[tokio::test] + async fn text_and_utf8_bytes_are_yielded_and_pongs_skipped() { + let harness = harness(); + let mut responses = harness.connection.responses(); + harness + .frames + .unbounded_send(Ok(ReceivedMessage::Pong)) + .expect("send frame"); + harness + .frames + .unbounded_send(Ok(ReceivedMessage::Text("hello".to_owned()))) + .expect("send frame"); + harness + .frames + .unbounded_send(Ok(ReceivedMessage::Bytes(b"raw".to_vec()))) + .expect("send frame"); + assert_eq!(responses.next().await.as_deref(), Some("hello")); + assert_eq!(responses.next().await.as_deref(), Some("raw")); + } + + #[tokio::test] + async fn receive_error_ends_the_stream() { + let harness = harness(); + let mut responses = harness.connection.responses(); + harness + .frames + .unbounded_send(Err(FakeError("boom"))) + .expect("send frame"); + assert_eq!(responses.next().await, None); + } + + #[tokio::test] + async fn second_responses_call_is_empty() { + let harness = harness(); + let _live = harness.connection.responses(); + let mut second = harness.connection.responses(); + assert_eq!(second.next().await, None); + } + + #[tokio::test] + async fn close_is_idempotent_ends_stream_and_closes_transport() { + let mut harness = harness(); + let mut responses = harness.connection.responses(); + harness.connection.close(); + harness.connection.close(); + assert_eq!(responses.next().await, None); + harness.connection.send("late".to_owned()); + // The writer drains the closed queue and then closes the transport. + assert_eq!(harness.sent.next().await, None); + tokio::task::yield_now().await; + assert!(*harness.sender_closed.lock().expect("lock")); + } + + #[tokio::test] + async fn writer_failure_ends_the_response_stream() { + // A dead socket (send fails) must end the responses stream so the + // consumer sees a disconnect instead of hanging on the request. + let (_frames_tx, frames_rx) = mpsc::unbounded::>(); + let connection = WsConnection::start(FailingSender, FakeReceiver { frames: frames_rx }); + let mut responses = connection.responses(); + connection.send("req".to_owned()); + assert_eq!(responses.next().await, None); + } + + #[tokio::test] + async fn a_full_request_buffer_ends_the_response_stream() { + // The writer stalls on the first request, so a tiny buffer fills and a + // later send overflows; the consumer must see the stream end, not hang. + let (_frames_tx, frames_rx) = mpsc::unbounded::>(); + let connection = + WsConnection::start_with_buffer(StalledSender, FakeReceiver { frames: frames_rx }, 2); + let mut responses = connection.responses(); + for _ in 0..64 { + connection.send("req".to_owned()); + } + assert_eq!(responses.next().await, None); + } + + #[tokio::test] + async fn drop_closes_the_connection() { + let harness = harness(); + let mut responses = harness.connection.responses(); + drop(harness.connection); + assert_eq!(responses.next().await, None); + } +} diff --git a/rust/crates/truapi-provider/src/ws_web.rs b/rust/crates/truapi-provider/src/ws_web.rs new file mode 100644 index 000000000..c0b1308c4 --- /dev/null +++ b/rust/crates/truapi-provider/src/ws_web.rs @@ -0,0 +1,216 @@ +//! Remote WebSocket JSON-RPC backend for `wasm32` (browser) targets. +//! +//! Mirrors the native backend's contract over the browser's `WebSocket`: a +//! raw string pipe whose responses stream ends when the socket dies. wasm is +//! single-threaded, so `SendWrapper` satisfies the trait's `Send + Sync` +//! bounds without real cross-thread use — calling a connection from another +//! thread would panic, and no such thread exists in this environment. + +use core::cell::RefCell; +use core::sync::atomic::{AtomicBool, Ordering}; +use std::rc::Rc; +use std::sync::Mutex; + +use futures::channel::{mpsc, oneshot}; +use futures::stream::{BoxStream, StreamExt}; +use send_wrapper::SendWrapper; +use truapi_platform::JsonRpcConnection; +use url::Url; + +use crate::error::ProviderError; +use wasm_bindgen::JsCast; +use wasm_bindgen::closure::Closure; +use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket}; + +/// The JS event callbacks kept alive for the socket's lifetime +/// (onmessage, onopen, onerror, onclose). +type SocketCallbacks = ( + Closure, + Closure, + Closure, + Closure, +); + +/// The socket and its live JS callbacks, wrapped so the connect future (which +/// the trait requires to be `Send`) can hold them across the handshake await. +type StagedSocket = SendWrapper<(WebSocket, SocketCallbacks)>; + +/// Open a browser WebSocket connection to `url`. +pub(crate) async fn connect(url: Url) -> Result, ProviderError> { + let (staged, handshake_rx, responses_tx, responses_rx) = open_socket(&url)?; + + match handshake_rx.await { + Ok(Ok(())) => {} + Ok(Err(reason)) => { + return Err(ProviderError::Handshake { + url: url.to_string(), + reason, + }); + } + Err(_) => { + return Err(ProviderError::Handshake { + url: url.to_string(), + reason: "handshake abandoned".to_owned(), + }); + } + } + + let (socket, callbacks) = staged.take(); + Ok(Box::new(WebWsConnection { + socket: SendWrapper::new(socket), + _callbacks: SendWrapper::new(callbacks), + responses_close: responses_tx, + responses: Mutex::new(Some(responses_rx.boxed())), + closed: AtomicBool::new(false), + })) +} + +/// Create the socket and wire its callbacks. +/// +/// Synchronous on purpose: every non-`Send` JS value is created and wrapped +/// here so the awaiting caller only holds `Send` state. +#[allow(clippy::type_complexity)] +fn open_socket( + url: &Url, +) -> Result< + ( + StagedSocket, + oneshot::Receiver>, + mpsc::UnboundedSender, + mpsc::UnboundedReceiver, + ), + ProviderError, +> { + let socket = WebSocket::new(url.as_str()).map_err(|err| ProviderError::Transport { + reason: format!("WebSocket creation for {url} failed: {err:?}"), + })?; + socket.set_binary_type(BinaryType::Arraybuffer); + + // Unbounded on purpose: the browser `onmessage` callback cannot apply + // backpressure, so a bound would force dropping inbound frames and break the + // consumer's id correlation. Depth is governed by the consumer draining the + // responses stream. + let (responses_tx, responses_rx) = mpsc::unbounded::(); + // Resolved exactly once by whichever of onopen/onerror/onclose fires + // first, so the handshake can be awaited. + let handshake = Rc::new(RefCell::new(None::>>)); + let (handshake_tx, handshake_rx) = oneshot::channel(); + *handshake.borrow_mut() = Some(handshake_tx); + + let onmessage = { + let responses_tx = responses_tx.clone(); + Closure::::new(move |event: MessageEvent| { + if let Some(text) = event.data().as_string() { + let _ = responses_tx.unbounded_send(text); + } else if let Ok(buffer) = event.data().dyn_into::() { + let bytes = js_sys::Uint8Array::new(&buffer).to_vec(); + match String::from_utf8(bytes) { + Ok(text) => { + let _ = responses_tx.unbounded_send(text); + } + Err(_) => tracing::warn!("dropping non-UTF-8 binary WebSocket frame"), + } + } + }) + }; + let onopen = { + let handshake = Rc::clone(&handshake); + Closure::::new(move |_| { + if let Some(sender) = handshake.borrow_mut().take() { + let _ = sender.send(Ok(())); + } + }) + }; + let onerror = { + let handshake = Rc::clone(&handshake); + Closure::::new(move |_| { + if let Some(sender) = handshake.borrow_mut().take() { + let _ = sender.send(Err("WebSocket error during handshake".to_owned())); + } + }) + }; + let onclose = { + let handshake = Rc::clone(&handshake); + let responses_tx = responses_tx.clone(); + Closure::::new(move |event: CloseEvent| { + if let Some(sender) = handshake.borrow_mut().take() { + let _ = sender.send(Err(format!( + "WebSocket closed during handshake (code {})", + event.code() + ))); + } + // Ending the channel ends the responses stream — the disconnect + // signal consumers rely on. + responses_tx.close_channel(); + }) + }; + socket.set_onmessage(Some(onmessage.as_ref().unchecked_ref())); + socket.set_onopen(Some(onopen.as_ref().unchecked_ref())); + socket.set_onerror(Some(onerror.as_ref().unchecked_ref())); + socket.set_onclose(Some(onclose.as_ref().unchecked_ref())); + + Ok(( + SendWrapper::new((socket, (onmessage, onopen, onerror, onclose))), + handshake_rx, + responses_tx, + responses_rx, + )) +} + +/// A live browser WebSocket connection exposed as a raw JSON-RPC pipe. +struct WebWsConnection { + socket: SendWrapper, + /// Keeps the JS event callbacks alive for the socket's lifetime. + _callbacks: SendWrapper, + /// Sender half of the responses channel, kept to end the stream on close. + responses_close: mpsc::UnboundedSender, + responses: Mutex>>, + closed: AtomicBool, +} + +impl JsonRpcConnection for WebWsConnection { + fn send(&self, request: String) { + if self.closed.load(Ordering::SeqCst) { + return; + } + if let Err(err) = self.socket.send_with_str(&request) { + // A send failure on a browser WebSocket means the socket is dead. + // End the responses stream so the consumer — which correlates by + // id — sees a disconnect instead of hanging on a request that will + // never be answered. + tracing::warn!("WebSocket send failed: {err:?}"); + self.closed.store(true, Ordering::SeqCst); + self.responses_close.close_channel(); + } + } + + fn responses(&self) -> BoxStream<'static, String> { + match self + .responses + .lock() + .expect("responses mutex poisoned") + .take() + { + Some(responses) => responses, + None => futures::stream::empty().boxed(), + } + } + + fn close(&self) { + if self.closed.swap(true, Ordering::SeqCst) { + return; + } + let _ = self.socket.close(); + self.responses_close.close_channel(); + self.responses + .lock() + .expect("responses mutex poisoned") + .take(); + } +} + +impl Drop for WebWsConnection { + fn drop(&mut self) { + self.close(); + } +} diff --git a/rust/crates/truapi-provider/tests/fixtures/paseo.json b/rust/crates/truapi-provider/tests/fixtures/paseo.json new file mode 100644 index 000000000..982ee66c9 --- /dev/null +++ b/rust/crates/truapi-provider/tests/fixtures/paseo.json @@ -0,0 +1 @@ +{"name":"Paseo Testnet","id":"paseo","chainType":"Live","bootNodes":["/dns/paseo-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr","/dns/paseo-boot-ng.dwellir.com/tcp/30354/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr","/dns/boot-node.helikon.io/tcp/10020/p2p/12D3KooWBetfzZpf6tGihKrqCo5z854Ub4ZNAUUTRT6eYHNh7FYi","/dns/boot-node.helikon.io/tcp/10022/wss/p2p/12D3KooWBetfzZpf6tGihKrqCo5z854Ub4ZNAUUTRT6eYHNh7FYi","/dns/paseo.bootnodes.polkadotters.com/tcp/30538/p2p/12D3KooWPbbFy4TefEGTRF5eTYhq8LEzc4VAHdNUVCbY4nAnhqPP","/dns/paseo.bootnodes.polkadotters.com/tcp/30540/wss/p2p/12D3KooWPbbFy4TefEGTRF5eTYhq8LEzc4VAHdNUVCbY4nAnhqPP","/dns/paseo.boot.rotko.net/tcp/34001/p2p/12D3KooWRH8eBMhw8c7bucy6pJfy94q4dKpLkF3pmeGohHmemdRu","/dns/paseo-bootnode.turboflakes.io/tcp/30630/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e","/dns/paseo-bootnode.turboflakes.io/tcp/30730/wss/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e"],"telemetryEndpoints":null,"protocolId":"pas","properties":{"ss58Format":0,"tokenDecimals":10,"tokenSymbol":"PAS"},"codeSubstitutes":{},"genesis":{"stateRootHash":"0x2b2a8395a8ec27c54d322d3a6602152da0e3bd0c8f4c01f17a572a44a8e36ab6"},"lightSyncState":{"babeEpochChanges":"0x04192b5af251dd3e410cf7347db2ccf3884c90c48b18306d232a6d8c92fd2a5e437c17ad000118e2a6110000000070e4a6110000000004b00b81c6b8f14d6e5de86369afb7d2b3c549cf0658d46dd5d86e57aa54b8faf4d419ad000170e4a61100000000c8e6a611000000000818e25b3b732fd524fa7125fb5f3ef3ab0f93d8db0a01dc33e9dffed6454a8391281cad0001c8e6a6110000000020e9a61100000000001e9b134f74c0190b13e39a6e9ad06759b1313bd6f81b1a4a56ed9938f5b2bff1281cad0001c8e6a6110000000020e9a6110000000000001018e25b3b732fd524fa7125fb5f3ef3ab0f93d8db0a01dc33e9dffed6454a8391281cad00015a4a000000000000c8e6a61100000000580200000000000061021004c021ae3e2d15f18d90800390836c5b30ca712d2f11c98e6ce41c3398175e010000000000000052fdf822726bb7479e08d00807b958bbc1240cc192c9b181e55437adabb5280001000000000000004e18d3de91affc85026448233d8c2cbc22d2f9ac49cbc7995e9c81b51a43cc6e010000000000000044454c1621aee6e3c1d9c3fb5026590d26be8add25900a394e24cf4138fc351001000000000000005e6f361a74bebe89866eb73d4fbe8689acfbecfaafeabc40e43236e656f22e39010000000000000094f97236c3431614d5b4deb6122dbd59eb801176dddb0ae30bce8afa1051d50b010000000000000012a9842734de09ea4847d09f1cbbbef40a44ea00733501c8933cf52442463228010000000000000010b5c4551c264f14cb2d5db4d5b50552d69534e013a062a9cb03d766df48ef4e010000000000000088435dc373ebb523a73f6d5e1496dd90627052f7e9369e38bde2581e5a9aca1f01000000000000003a54928c21dbe6d0d8ce229ae899640194f962821a4faa6a911fa8fda1809b7a01000000000000005abfafd5b636a5263653f7217d74a02711a4f13962c3f694f875f6d0cf1b357a010000000000000088ed4c0c51838ad383c7554841f76c6f623da39f948110f927bc0701a8c8960f0100000000000000c8c8cdaa16a9102f49d4fc3ed3d14e09176987c85a3cdb31406b9694996a3d0f0100000000000000eabeed18aba996fff73a024926d079fde07fc9612d4de305bf630f7bd34db7510100000000000000dada9cf6148af40fccdda9586e25c50088651f0dcfe6a9a6057dafe4d9ea8e06010000000000000068ba40acfe65dd1daa70dd5c6c3fba664e0cf005e175f0204deb1c5d1d15f454010000000000000050073bf942fbba7324a63e6c174d99240bd649bd5d0d501ca75bd259f2fa34510100000000000000c0d7173ab223397c821c26326e935363b12fe7422323a0c34178630cc2c91d3501000000000000006aa461fba284ea42deadaa35f2c255616cffc121334ee4b3303da5bad29f79340100000000000000daa36dfb87d7ba34e315b4e31388ae6cba278cdb9896c417efa5c2aac97a927b01000000000000009ab2927908f20efe0aa8218bb35584aab8c12ed8296d35ff52fdc09cd31ec744010000000000000044bd6d820dc0b0aa0e896331ff92a49fb13d4c9a1cba4a09cfcdeed6f9b78532010000000000000018fe1ca74d50a35d5c036bc87c52e3b0d5630f7b84ead37963388a5464e0d4490100000000000000e48535befb2e807e3d0cc182fe9e05a2d3a8bac4dec6448aae7eab5fa86956250100000000000000d81837593758987c9ef04f350de913a8a9ceaaefacb1ce4f54e27512e047d0090100000000000000724281a8cd7ff6cf4fb578ca8fc4bc9e41a3c223e10c5f11ec50acace91ef0710100000000000000d8d17c130c3b9281eb825c8020ecfb34823fdcb5b1423a4437e9ba1ff137d67701000000000000002a0cd19f595d69c11038897ee10a39bb8a05b3f254a4649bfa5a4c8b658b540a01000000000000007459633fb56901747ace72a8371eeb57c1694aaefae3e026fd5a4a288c295d160100000000000000508b057341d4a6801eff555343dcf7f427b5dc97f711f702e34074821e28d43a0100000000000000b29445ff1e828bbbf47c3f513efee4a5a23366620033ac53b8408d89a50d85170100000000000000f0306d3da4e222bda6617ba21253d8e1c59853edf60d9cfd28ca4f6dc6eacb2a010000000000000062d30d7886fc9cbb5ce8d48b9df7f5b94906f633a7cc9f6fd3000ef1257aa46501000000000000001c1420b31f9900d967e3dc211dd4a35e446362ecfbf086903ebf420a9e35b77e010000000000000018f7c58fb2cf70d650f445d474d2be3fb9e8cec4a71bccf0effc229a8784df610100000000000000f8a98c9f6100847f98bbef8ff3a4c9a7145f770cd0d127767dae18bba50bda4601000000000000002cca14fe72f6c3b74b58957566916f051a17835bfdfe68568a05f501f499f87001000000000000009ef6a9bd209d70f858a0eccdbc1cfe4e9438967cd75c395c4552b2d3ce05ab520100000000000000da5e5836d77f80d1196569fd401327b8f59d03251921d4e1740327e39e23da6b010000000000000092ee42aa9270f21adfd9f282ffb73b3e942fc06038bc83de35d8e9971fdde3080100000000000000c8f17d9b568dc9792cd03c61a1342e81afbc1dcd3f5fe54d5a85fd50ad85b6230100000000000000402a56dc42d0874e9494013cbcfa9b8a8e38f36a8a64190a3b3cbef43ac0643e01000000000000002887dcb68a706130ac662baa3021d73f5204b56aad3d87eff0ba7774f18ce54b0100000000000000182e505cd7b3d53b33aa96b6729c182c335026cfe3b16e9b02be3bae64d80d2301000000000000001cf887bde697b016fc21f0bb5cc432a02f1604d7618944bb0a8b332565db2f250100000000000000c2271f77443650b87c58eafb27ef622759ce18eba3f11d5c790d90113b5baa390100000000000000dc58296ce30054fc942543d1058bd81d994ccf73ffc4fe7af1bfaaab1ab4ab180100000000000000289ea60fad4ef35fa3a1743a46d23b7c7530ac4497002b205e6b30ed69604a290100000000000000d8eda667e774930626e2ca3fc676559fa14e5e573fb3d86addf9fcc57bb9ea32010000000000000088f9078eac35965f1868f68f8759ba030ceb7d28bd503b7df9f39fd38e87e25a0100000000000000d23382c75de4f812e29e8895c880793b4ab4fd3d28790944675499e255d4774b0100000000000000660fe756513cbcabaeed97224784cef9f128a4d4b02bb4fa004ed9663feab90f0100000000000000b60d525db7d60ac59d4ccffa3afd8a1094a080729c0d19c7b539b0191c52640101000000000000000c0bcbe2e6ff076aac9f38fed2e301c08447fafc07e085a0980beb3fab0ed526010000000000000060d29f2aa543abe8e1f102a5f106a6496f63298103d9231f730721bc62116e1c01000000000000009a883590ad1970522f048f8ead7b0e6dbb4a586bd068ace45201fada8981c8770100000000000000e6a08e00ddfe7e2158d83bce39035533eca9502c8693d44c2baa472e2ea63d4701000000000000007ee37a4d18261a43aeaa08030dada2fa2fa0b9dfbf4aa39e36973c6b1a2fdb5e010000000000000062d1095d44ec36d6b76f22ef82ac0f75505f722ea0627ae0bfe5e261f503823f010000000000000008f7dded97687143c275e5e947f1b335366a3d207c88df746a0c1c40af33b57e0100000000000000badb0b0033fb149dcb82e55a3a27ccc846c9572c13f88a8fb927e56903106971010000000000000042d092dfc6e035ea66fe617d8523dafec8d46cfe4db19ff22bab8355b483854c0100000000000000a8c0111411f9ae214c425b21a708841db2c094bae35951fe738d806d3349985f01000000000000009e4a15681b6086bede043fcd452d68426aee3afe74a9fccbca015a8d120bb207010000000000000050964893494da29e8824ab45493d956a784bdb31ec4b2311a2906c6ad1bab11e01000000000000001a24a7e0ab1ba604832e179a20f1b221d632f340749cc8a01de7aabcf098ef51010000000000000092f78f633dddc2d47a49b48a6684826c5447afea2ddf50875010298352280d050100000000000000fa26b1a6aac636a1b8d7db8b177f3f7b3915c5b287e86f06dd9015b815512b410100000000000000640077e1a2175b4d734aa5e597c1f28bc09817b9c5d3af37b24314e80da56f4d0100000000000000e007bad84209b4a4ca5110d717b98050ca2d3712b5e2795a991ff62c8e217e2901000000000000007a911bf0ab42098d8db035743bd4f7b7a5ed91a44508837da94e111357e6801c0100000000000000a8f2fa12f1bd4245d7af8390f9fcc9206aed550ff8366bf81412e2501f8b5a0701000000000000008e0b120eea5193f6731daac3fd19cd744191e250592e190797d0c9273912fa1901000000000000003af2e1ce5fc087258447fc497f472912caa1fa0702573884c6020fac3cbb74510100000000000000cafcf18b2d1f6a60bf52328011c25068f1ae49e109f4d45ca194a7acd5a8f0170100000000000000926fd4ade255b73ad4a4dd444acd2f4565deb2336223d661d9f1781d9f729426010000000000000016b71bd30e9748c8c89e5898fbf0f01e320fdd574aee590e6cac3ebaf0028c0301000000000000007640ba8105b5391aa56fef680822c673dc422b7d601c02cce4949dcece0ab85a0100000000000000f6850b653753d8a7231fe0f790f2f5fee8530743b41a2f8f4c65677a09144128010000000000000028e5fe1fc01920b017233d4debce9179791c8e60995f950756258c17f3e626150100000000000000123d59ae5331208e9810e9354ba4cb7466ca49d078e9086adb699254ca457d3b01000000000000001aa715bb53e904ba98cfe1bf2908c6ccdc952d6a584ba967e560c94d61275b640100000000000000200c96621fa81f67fb63bd35aa62b28be6d5ffafda169e126bba66332124013e0100000000000000b0df64620c6245803cc675496b3d28150fef6bc966684ca524908dd53bc6605d01000000000000004c677e26e24ecf36659011fbc9ecfb87f98e2192102ba4ec7eb2a307014c28140100000000000000c05be111614164f811b6d792a9a5defb1bd1bfb6712b8f6632719ec47ebcbe3a0100000000000000e6a82cb09111b1828030ebcba68f066cc8a2f0d7032edf102463023a571ed51201000000000000001a52989f498960edd384cdd3f52b11984912325ae290c3050886d009bf036f7b0100000000000000027e4cfaeaae32857c2595dcc4b696790a16c313b193dcc43bfe6ee593dfca650100000000000000cafdce7b198eeede8fbb8e6e44d43ea6aa5c152577a89989002968798c87b737010000000000000054d303a6f6f58c705f3bf9e03bbcfd5fc5e78d77844e0c557782da67a34fd10d0100000000000000e0e9401a2554ae5ed8623a6a0dd4dd8115b0112262cbeac006a4adfd7929ae5001000000000000001422cb0f567ee5f431a7a871f43e8602d7e0b172dd6bf2819ca2a46e6a87c9600100000000000000fc2d047eed5fa2dfb6d7117c177ef29748d93127826e79cf6824ae863dad9a440100000000000000940491aa5c11a7dceac769beea3d4a3815f995b30f995daff3c0410ff3ec4c130100000000000000ea347d23e3bf4f11047d1580444e7f6e2797cfecafa435aace72fbc5b4a51826010000000000000022fc849fcf86c5a7b583c6580597c141c56c3115298d49a0a36d6b7e441b6f52010000000000000088ba9223502f96209814c38bec4994fe2129793f357b07aca4ede940241cd95001000000000000002c016acd6877946eb48b83a1dde11059adb5044428ce43809d3216de6d92240d0100000000000000bec6ff2a5ae2411507a472960fe1297ea1f4a197d4f5346eb71b2f2327a9504801000000000000008eb5892277e6af39c9c440dfc7554ca68b7fb10eeeb9ebc33b24815de6bb90250100000000000000867404b8820644b7ad4d620d7472dbd6b5f761e924cb364651959e06b1975e57010000000000000088da7347f8d84f63a8f499d384aeb6977dd7b840b12ead209abe4c86ab9312650100000000000000f4c75a45902b4aba440cce1947300008a6fdbc6a988fb8647edf915b99fef875010000000000000040319d4152ef99f4dae00d763969f766c3f2421a1c3a213dcad56fefbfb8c35d0100000000000000e01b84f0da8be0b1807c5cb83889bbe5148a1e8a96b0aeb9b4ae15fe674588680100000000000000e6f18b461c86aa7ad1b300ac15d9611011b44c9a10f64cd2938d6bb52be03c120100000000000000be343eab7637aa2890bbf849b345f2540ceb74ebc9a3cc86888a67902bd3e91c010000000000000020089c94aa9b42573c668eb666a7049873132508a078cc3eba7bbab25704100d01000000000000001ef72e689e5d465cb2572fafe50d869d28b219d315c18d2ac9d3f6b8ec610c6e0100000000000000302d97fc1992127356b91159e3a1d9ac27d9ccc65732d124287f1fbfb8e24a7c010000000000000028114b62e53ee6aa11fc6d5cf4c70aad10dc503a0c1d801665c176a9052e354b0100000000000000787cdecf233c0f0730da3ddbcf8c5abb0fd501f3ffe2dbd252b5bb031d207c6b0100000000000000765e230454ea87385e7158e57f21dcf67b0922344ea879638e821c51f4d4a00f0100000000000000f8eb06c255edf56235fe6d613042bb2a2a21df3002a453ad34e91d5e00d6ae650100000000000000bce7396a75dc0c6686c24d42ebc720c1fe2e38b5790a60cb90d5bd0da736aa58010000000000000026c8e0e882482190ae9d58ff7c914e881024de60fd7e0f207c83357f30acaf1901000000000000006441ff8df4d2619237e696f8103737f3b003bf66267fb681f7df831021ff441a01000000000000005a8b4fe4cc6ddbb01f141c2565530bc9677e6d6baaa50ad80a529c4e9094f31e0100000000000000ce566e342591b85edcca0c57c4ed252ae80ba9e4298fdfa0c5d984a6bdc72b4401000000000000007e18f0ebfbac50554a56cc4f1bdcf70642d36393f9f0253f55518557b35bfb4c01000000000000005a6da85d909fd6baf57b8cc915cb27b6d8ec6d39247f146aa0109bd59d17d8050100000000000000e8cbd70d0678b15fdc01adcc0a1442836332f30d55d89661ae51191a09ae5a150100000000000000cae6f66d652565c27aa90259bc04296eb4962e6d8ab8b3eee1455174d52f24490100000000000000960f63edec5036689e60e56a0f2eb75f45e4ab5befb36f03b10c988d9eff6a45010000000000000052d5c76808348ba5e350ce32b94b225a7f1c577a8899811a10efe0c18316634d01000000000000000845f4903a8500c8aaae9ea4a7d6d018f4cd148e958e2f723fccebbdc982dd0601000000000000009a8551aa56ddba67f238f2870e995f5e5c14636e72815e8ef671f97ab8debd6b01000000000000008eeaba1502468d954e19f3e28e0aae48196d229a6eb5762b22ea2fe431b5a85101000000000000009c15c7a9ed43f74f816de4ba1c6d78d92bb873ede60ea4c75e9781807157524201000000000000008a0230f16fdb15c9855d211fcbd3a985819a8557ef6907cfdf0b44645e617057010000000000000058b08c94c313efda4bba48778afc50e540702b527d2d583d38f3a83ab639c85c0100000000000000f08c9a0d1e9ea3491c6bbdf94733ddcb07ad16cb75953748946c4b65c1628847010000000000000056854f0d2a2deeb5729c37d65334ad98a193fd29847f92cd7cba0935f33ee8140100000000000000f8fe5e8c5e2173894a49cabe37a9ba068c383043bf90d8694f37373c3d401a200100000000000000c2e8426228531975cb2963d57bff68b31949840187b43a43836e43a247350c470100000000000000408f0da37f48afc23014b52c52b4de90b04cb47f0021500983800f891a9ad0350100000000000000c6e0d3d715fa693dc0fec013c0d58cfeab2698b1da6472076b9ac9aecc3f70350100000000000000022cedafe7d099e6c4c6063b07daed9c9a4f85a14f01a2524a3a66710cff0d1801000000000000001074454308ee7ea6f14915f6292003376a0c4a7f7d47bfdf7c03fee7217e3f0401000000000000000215a230c8a0056fbd42596c7d707fbad5a04d2058ac6e485ea57ab97ec358740100000000000000f087ec6e70b76d03309c82b4e3721a7d23b487c29953a33f6bb1f4faa3930026010000000000000088d889391d92f6ac6d6dcc195599375b4db24e414924690166f8ad1eb7b5b6070100000000000000d86dbadd9c4938544b946d2d2ddb57a963aeb1d4a7f34566edbeab1873ca357c0100000000000000a84b258720551c4c0f1759c6c82316468000547da08a3c0326de3f9d71c353470100000000000000d0f8ecef301c4ed00db8a84c4e2849192f80428adc7703d376572b70136c755c01000000000000004c9ea41ce997ef20f010009767f91a6e71379f1676b234bdfe63c5fba2b7300c01000000000000001267d8885ac15dd9c3dc1fb28a3604963bd650ca2a99a097e0aa7b56b6a41d2c0100000000000000ec28e2aa5c3b96b46a8082a6cdee58e6b8eec81380b0fc14c9809f750b7250540100000000000000c6e1836fd76d6c06dc9b277d4b0782e8973c0655244d59f9f6712d161f15502201000000000000007a2886724b12f97d1b7ad788067b082286f3cd47f0f701f415214526da8a4d67010000000000000006b7a572d089bcd98935562a8996b4b2f6de5814887c7cad1ccf5cc2d23656330100000000000000d858bae50a7f08d01d2d73c63b5f106e04e95d0d794d361697e863254120fa5e0100000000000000040000000000000002192b5af251dd3e410cf7347db2ccf3884c90c48b18306d232a6d8c92fd2a5e437c17ad0001584a00000000000018e2a61100000000580200000000000061021004c021ae3e2d15f18d90800390836c5b30ca712d2f11c98e6ce41c3398175e010000000000000052fdf822726bb7479e08d00807b958bbc1240cc192c9b181e55437adabb5280001000000000000004e18d3de91affc85026448233d8c2cbc22d2f9ac49cbc7995e9c81b51a43cc6e010000000000000044454c1621aee6e3c1d9c3fb5026590d26be8add25900a394e24cf4138fc351001000000000000005e6f361a74bebe89866eb73d4fbe8689acfbecfaafeabc40e43236e656f22e39010000000000000094f97236c3431614d5b4deb6122dbd59eb801176dddb0ae30bce8afa1051d50b010000000000000012a9842734de09ea4847d09f1cbbbef40a44ea00733501c8933cf52442463228010000000000000010b5c4551c264f14cb2d5db4d5b50552d69534e013a062a9cb03d766df48ef4e010000000000000088435dc373ebb523a73f6d5e1496dd90627052f7e9369e38bde2581e5a9aca1f01000000000000003a54928c21dbe6d0d8ce229ae899640194f962821a4faa6a911fa8fda1809b7a01000000000000005abfafd5b636a5263653f7217d74a02711a4f13962c3f694f875f6d0cf1b357a010000000000000088ed4c0c51838ad383c7554841f76c6f623da39f948110f927bc0701a8c8960f0100000000000000c8c8cdaa16a9102f49d4fc3ed3d14e09176987c85a3cdb31406b9694996a3d0f0100000000000000eabeed18aba996fff73a024926d079fde07fc9612d4de305bf630f7bd34db7510100000000000000dada9cf6148af40fccdda9586e25c50088651f0dcfe6a9a6057dafe4d9ea8e06010000000000000068ba40acfe65dd1daa70dd5c6c3fba664e0cf005e175f0204deb1c5d1d15f454010000000000000050073bf942fbba7324a63e6c174d99240bd649bd5d0d501ca75bd259f2fa34510100000000000000c0d7173ab223397c821c26326e935363b12fe7422323a0c34178630cc2c91d3501000000000000006aa461fba284ea42deadaa35f2c255616cffc121334ee4b3303da5bad29f79340100000000000000daa36dfb87d7ba34e315b4e31388ae6cba278cdb9896c417efa5c2aac97a927b01000000000000009ab2927908f20efe0aa8218bb35584aab8c12ed8296d35ff52fdc09cd31ec744010000000000000044bd6d820dc0b0aa0e896331ff92a49fb13d4c9a1cba4a09cfcdeed6f9b78532010000000000000018fe1ca74d50a35d5c036bc87c52e3b0d5630f7b84ead37963388a5464e0d4490100000000000000e48535befb2e807e3d0cc182fe9e05a2d3a8bac4dec6448aae7eab5fa86956250100000000000000d81837593758987c9ef04f350de913a8a9ceaaefacb1ce4f54e27512e047d0090100000000000000724281a8cd7ff6cf4fb578ca8fc4bc9e41a3c223e10c5f11ec50acace91ef0710100000000000000d8d17c130c3b9281eb825c8020ecfb34823fdcb5b1423a4437e9ba1ff137d67701000000000000002a0cd19f595d69c11038897ee10a39bb8a05b3f254a4649bfa5a4c8b658b540a01000000000000007459633fb56901747ace72a8371eeb57c1694aaefae3e026fd5a4a288c295d160100000000000000508b057341d4a6801eff555343dcf7f427b5dc97f711f702e34074821e28d43a0100000000000000b29445ff1e828bbbf47c3f513efee4a5a23366620033ac53b8408d89a50d85170100000000000000f0306d3da4e222bda6617ba21253d8e1c59853edf60d9cfd28ca4f6dc6eacb2a010000000000000062d30d7886fc9cbb5ce8d48b9df7f5b94906f633a7cc9f6fd3000ef1257aa46501000000000000001c1420b31f9900d967e3dc211dd4a35e446362ecfbf086903ebf420a9e35b77e010000000000000018f7c58fb2cf70d650f445d474d2be3fb9e8cec4a71bccf0effc229a8784df610100000000000000f8a98c9f6100847f98bbef8ff3a4c9a7145f770cd0d127767dae18bba50bda4601000000000000002cca14fe72f6c3b74b58957566916f051a17835bfdfe68568a05f501f499f87001000000000000009ef6a9bd209d70f858a0eccdbc1cfe4e9438967cd75c395c4552b2d3ce05ab520100000000000000da5e5836d77f80d1196569fd401327b8f59d03251921d4e1740327e39e23da6b010000000000000092ee42aa9270f21adfd9f282ffb73b3e942fc06038bc83de35d8e9971fdde3080100000000000000c8f17d9b568dc9792cd03c61a1342e81afbc1dcd3f5fe54d5a85fd50ad85b6230100000000000000402a56dc42d0874e9494013cbcfa9b8a8e38f36a8a64190a3b3cbef43ac0643e01000000000000002887dcb68a706130ac662baa3021d73f5204b56aad3d87eff0ba7774f18ce54b0100000000000000182e505cd7b3d53b33aa96b6729c182c335026cfe3b16e9b02be3bae64d80d2301000000000000001cf887bde697b016fc21f0bb5cc432a02f1604d7618944bb0a8b332565db2f250100000000000000c2271f77443650b87c58eafb27ef622759ce18eba3f11d5c790d90113b5baa390100000000000000dc58296ce30054fc942543d1058bd81d994ccf73ffc4fe7af1bfaaab1ab4ab180100000000000000289ea60fad4ef35fa3a1743a46d23b7c7530ac4497002b205e6b30ed69604a290100000000000000d8eda667e774930626e2ca3fc676559fa14e5e573fb3d86addf9fcc57bb9ea32010000000000000088f9078eac35965f1868f68f8759ba030ceb7d28bd503b7df9f39fd38e87e25a0100000000000000d23382c75de4f812e29e8895c880793b4ab4fd3d28790944675499e255d4774b0100000000000000660fe756513cbcabaeed97224784cef9f128a4d4b02bb4fa004ed9663feab90f0100000000000000b60d525db7d60ac59d4ccffa3afd8a1094a080729c0d19c7b539b0191c52640101000000000000000c0bcbe2e6ff076aac9f38fed2e301c08447fafc07e085a0980beb3fab0ed526010000000000000060d29f2aa543abe8e1f102a5f106a6496f63298103d9231f730721bc62116e1c01000000000000009a883590ad1970522f048f8ead7b0e6dbb4a586bd068ace45201fada8981c8770100000000000000e6a08e00ddfe7e2158d83bce39035533eca9502c8693d44c2baa472e2ea63d4701000000000000007ee37a4d18261a43aeaa08030dada2fa2fa0b9dfbf4aa39e36973c6b1a2fdb5e010000000000000062d1095d44ec36d6b76f22ef82ac0f75505f722ea0627ae0bfe5e261f503823f010000000000000008f7dded97687143c275e5e947f1b335366a3d207c88df746a0c1c40af33b57e0100000000000000badb0b0033fb149dcb82e55a3a27ccc846c9572c13f88a8fb927e56903106971010000000000000042d092dfc6e035ea66fe617d8523dafec8d46cfe4db19ff22bab8355b483854c0100000000000000a8c0111411f9ae214c425b21a708841db2c094bae35951fe738d806d3349985f01000000000000009e4a15681b6086bede043fcd452d68426aee3afe74a9fccbca015a8d120bb207010000000000000050964893494da29e8824ab45493d956a784bdb31ec4b2311a2906c6ad1bab11e01000000000000001a24a7e0ab1ba604832e179a20f1b221d632f340749cc8a01de7aabcf098ef51010000000000000092f78f633dddc2d47a49b48a6684826c5447afea2ddf50875010298352280d050100000000000000fa26b1a6aac636a1b8d7db8b177f3f7b3915c5b287e86f06dd9015b815512b410100000000000000640077e1a2175b4d734aa5e597c1f28bc09817b9c5d3af37b24314e80da56f4d0100000000000000e007bad84209b4a4ca5110d717b98050ca2d3712b5e2795a991ff62c8e217e2901000000000000007a911bf0ab42098d8db035743bd4f7b7a5ed91a44508837da94e111357e6801c0100000000000000a8f2fa12f1bd4245d7af8390f9fcc9206aed550ff8366bf81412e2501f8b5a0701000000000000008e0b120eea5193f6731daac3fd19cd744191e250592e190797d0c9273912fa1901000000000000003af2e1ce5fc087258447fc497f472912caa1fa0702573884c6020fac3cbb74510100000000000000cafcf18b2d1f6a60bf52328011c25068f1ae49e109f4d45ca194a7acd5a8f0170100000000000000926fd4ade255b73ad4a4dd444acd2f4565deb2336223d661d9f1781d9f729426010000000000000016b71bd30e9748c8c89e5898fbf0f01e320fdd574aee590e6cac3ebaf0028c0301000000000000007640ba8105b5391aa56fef680822c673dc422b7d601c02cce4949dcece0ab85a0100000000000000f6850b653753d8a7231fe0f790f2f5fee8530743b41a2f8f4c65677a09144128010000000000000028e5fe1fc01920b017233d4debce9179791c8e60995f950756258c17f3e626150100000000000000123d59ae5331208e9810e9354ba4cb7466ca49d078e9086adb699254ca457d3b01000000000000001aa715bb53e904ba98cfe1bf2908c6ccdc952d6a584ba967e560c94d61275b640100000000000000200c96621fa81f67fb63bd35aa62b28be6d5ffafda169e126bba66332124013e0100000000000000b0df64620c6245803cc675496b3d28150fef6bc966684ca524908dd53bc6605d01000000000000004c677e26e24ecf36659011fbc9ecfb87f98e2192102ba4ec7eb2a307014c28140100000000000000c05be111614164f811b6d792a9a5defb1bd1bfb6712b8f6632719ec47ebcbe3a0100000000000000e6a82cb09111b1828030ebcba68f066cc8a2f0d7032edf102463023a571ed51201000000000000001a52989f498960edd384cdd3f52b11984912325ae290c3050886d009bf036f7b0100000000000000027e4cfaeaae32857c2595dcc4b696790a16c313b193dcc43bfe6ee593dfca650100000000000000cafdce7b198eeede8fbb8e6e44d43ea6aa5c152577a89989002968798c87b737010000000000000054d303a6f6f58c705f3bf9e03bbcfd5fc5e78d77844e0c557782da67a34fd10d0100000000000000e0e9401a2554ae5ed8623a6a0dd4dd8115b0112262cbeac006a4adfd7929ae5001000000000000001422cb0f567ee5f431a7a871f43e8602d7e0b172dd6bf2819ca2a46e6a87c9600100000000000000fc2d047eed5fa2dfb6d7117c177ef29748d93127826e79cf6824ae863dad9a440100000000000000940491aa5c11a7dceac769beea3d4a3815f995b30f995daff3c0410ff3ec4c130100000000000000ea347d23e3bf4f11047d1580444e7f6e2797cfecafa435aace72fbc5b4a51826010000000000000022fc849fcf86c5a7b583c6580597c141c56c3115298d49a0a36d6b7e441b6f52010000000000000088ba9223502f96209814c38bec4994fe2129793f357b07aca4ede940241cd95001000000000000002c016acd6877946eb48b83a1dde11059adb5044428ce43809d3216de6d92240d0100000000000000bec6ff2a5ae2411507a472960fe1297ea1f4a197d4f5346eb71b2f2327a9504801000000000000008eb5892277e6af39c9c440dfc7554ca68b7fb10eeeb9ebc33b24815de6bb90250100000000000000867404b8820644b7ad4d620d7472dbd6b5f761e924cb364651959e06b1975e57010000000000000088da7347f8d84f63a8f499d384aeb6977dd7b840b12ead209abe4c86ab9312650100000000000000f4c75a45902b4aba440cce1947300008a6fdbc6a988fb8647edf915b99fef875010000000000000040319d4152ef99f4dae00d763969f766c3f2421a1c3a213dcad56fefbfb8c35d0100000000000000e01b84f0da8be0b1807c5cb83889bbe5148a1e8a96b0aeb9b4ae15fe674588680100000000000000e6f18b461c86aa7ad1b300ac15d9611011b44c9a10f64cd2938d6bb52be03c120100000000000000be343eab7637aa2890bbf849b345f2540ceb74ebc9a3cc86888a67902bd3e91c010000000000000020089c94aa9b42573c668eb666a7049873132508a078cc3eba7bbab25704100d01000000000000001ef72e689e5d465cb2572fafe50d869d28b219d315c18d2ac9d3f6b8ec610c6e0100000000000000302d97fc1992127356b91159e3a1d9ac27d9ccc65732d124287f1fbfb8e24a7c010000000000000028114b62e53ee6aa11fc6d5cf4c70aad10dc503a0c1d801665c176a9052e354b0100000000000000787cdecf233c0f0730da3ddbcf8c5abb0fd501f3ffe2dbd252b5bb031d207c6b0100000000000000765e230454ea87385e7158e57f21dcf67b0922344ea879638e821c51f4d4a00f0100000000000000f8eb06c255edf56235fe6d613042bb2a2a21df3002a453ad34e91d5e00d6ae650100000000000000bce7396a75dc0c6686c24d42ebc720c1fe2e38b5790a60cb90d5bd0da736aa58010000000000000026c8e0e882482190ae9d58ff7c914e881024de60fd7e0f207c83357f30acaf1901000000000000006441ff8df4d2619237e696f8103737f3b003bf66267fb681f7df831021ff441a01000000000000005a8b4fe4cc6ddbb01f141c2565530bc9677e6d6baaa50ad80a529c4e9094f31e0100000000000000ce566e342591b85edcca0c57c4ed252ae80ba9e4298fdfa0c5d984a6bdc72b4401000000000000007e18f0ebfbac50554a56cc4f1bdcf70642d36393f9f0253f55518557b35bfb4c01000000000000005a6da85d909fd6baf57b8cc915cb27b6d8ec6d39247f146aa0109bd59d17d8050100000000000000e8cbd70d0678b15fdc01adcc0a1442836332f30d55d89661ae51191a09ae5a150100000000000000cae6f66d652565c27aa90259bc04296eb4962e6d8ab8b3eee1455174d52f24490100000000000000960f63edec5036689e60e56a0f2eb75f45e4ab5befb36f03b10c988d9eff6a45010000000000000052d5c76808348ba5e350ce32b94b225a7f1c577a8899811a10efe0c18316634d01000000000000000845f4903a8500c8aaae9ea4a7d6d018f4cd148e958e2f723fccebbdc982dd0601000000000000009a8551aa56ddba67f238f2870e995f5e5c14636e72815e8ef671f97ab8debd6b01000000000000008eeaba1502468d954e19f3e28e0aae48196d229a6eb5762b22ea2fe431b5a85101000000000000009c15c7a9ed43f74f816de4ba1c6d78d92bb873ede60ea4c75e9781807157524201000000000000008a0230f16fdb15c9855d211fcbd3a985819a8557ef6907cfdf0b44645e617057010000000000000058b08c94c313efda4bba48778afc50e540702b527d2d583d38f3a83ab639c85c0100000000000000f08c9a0d1e9ea3491c6bbdf94733ddcb07ad16cb75953748946c4b65c1628847010000000000000056854f0d2a2deeb5729c37d65334ad98a193fd29847f92cd7cba0935f33ee8140100000000000000f8fe5e8c5e2173894a49cabe37a9ba068c383043bf90d8694f37373c3d401a200100000000000000c2e8426228531975cb2963d57bff68b31949840187b43a43836e43a247350c470100000000000000408f0da37f48afc23014b52c52b4de90b04cb47f0021500983800f891a9ad0350100000000000000c6e0d3d715fa693dc0fec013c0d58cfeab2698b1da6472076b9ac9aecc3f70350100000000000000022cedafe7d099e6c4c6063b07daed9c9a4f85a14f01a2524a3a66710cff0d1801000000000000001074454308ee7ea6f14915f6292003376a0c4a7f7d47bfdf7c03fee7217e3f0401000000000000000215a230c8a0056fbd42596c7d707fbad5a04d2058ac6e485ea57ab97ec358740100000000000000f087ec6e70b76d03309c82b4e3721a7d23b487c29953a33f6bb1f4faa3930026010000000000000088d889391d92f6ac6d6dcc195599375b4db24e414924690166f8ad1eb7b5b6070100000000000000d86dbadd9c4938544b946d2d2ddb57a963aeb1d4a7f34566edbeab1873ca357c0100000000000000a84b258720551c4c0f1759c6c82316468000547da08a3c0326de3f9d71c353470100000000000000d0f8ecef301c4ed00db8a84c4e2849192f80428adc7703d376572b70136c755c01000000000000004c9ea41ce997ef20f010009767f91a6e71379f1676b234bdfe63c5fba2b7300c01000000000000001267d8885ac15dd9c3dc1fb28a3604963bd650ca2a99a097e0aa7b56b6a41d2c0100000000000000ec28e2aa5c3b96b46a8082a6cdee58e6b8eec81380b0fc14c9809f750b7250540100000000000000c6e1836fd76d6c06dc9b277d4b0782e8973c0655244d59f9f6712d161f15502201000000000000007a2886724b12f97d1b7ad788067b082286f3cd47f0f701f415214526da8a4d67010000000000000006b7a572d089bcd98935562a8996b4b2f6de5814887c7cad1ccf5cc2d23656330100000000000000a47536f9ac9077d95a65dd18f09c4bf8de2a4016da77f46f89bc843a525fbd8e01000000000000000400000000000000021e9b134f74c0190b13e39a6e9ad06759b1313bd6f81b1a4a56ed9938f5b2bff1281cad00015a4a000000000000c8e6a61100000000580200000000000061021004c021ae3e2d15f18d90800390836c5b30ca712d2f11c98e6ce41c3398175e010000000000000052fdf822726bb7479e08d00807b958bbc1240cc192c9b181e55437adabb5280001000000000000004e18d3de91affc85026448233d8c2cbc22d2f9ac49cbc7995e9c81b51a43cc6e010000000000000044454c1621aee6e3c1d9c3fb5026590d26be8add25900a394e24cf4138fc351001000000000000005e6f361a74bebe89866eb73d4fbe8689acfbecfaafeabc40e43236e656f22e39010000000000000094f97236c3431614d5b4deb6122dbd59eb801176dddb0ae30bce8afa1051d50b010000000000000012a9842734de09ea4847d09f1cbbbef40a44ea00733501c8933cf52442463228010000000000000010b5c4551c264f14cb2d5db4d5b50552d69534e013a062a9cb03d766df48ef4e010000000000000088435dc373ebb523a73f6d5e1496dd90627052f7e9369e38bde2581e5a9aca1f01000000000000003a54928c21dbe6d0d8ce229ae899640194f962821a4faa6a911fa8fda1809b7a01000000000000005abfafd5b636a5263653f7217d74a02711a4f13962c3f694f875f6d0cf1b357a010000000000000088ed4c0c51838ad383c7554841f76c6f623da39f948110f927bc0701a8c8960f0100000000000000c8c8cdaa16a9102f49d4fc3ed3d14e09176987c85a3cdb31406b9694996a3d0f0100000000000000eabeed18aba996fff73a024926d079fde07fc9612d4de305bf630f7bd34db7510100000000000000dada9cf6148af40fccdda9586e25c50088651f0dcfe6a9a6057dafe4d9ea8e06010000000000000068ba40acfe65dd1daa70dd5c6c3fba664e0cf005e175f0204deb1c5d1d15f454010000000000000050073bf942fbba7324a63e6c174d99240bd649bd5d0d501ca75bd259f2fa34510100000000000000c0d7173ab223397c821c26326e935363b12fe7422323a0c34178630cc2c91d3501000000000000006aa461fba284ea42deadaa35f2c255616cffc121334ee4b3303da5bad29f79340100000000000000daa36dfb87d7ba34e315b4e31388ae6cba278cdb9896c417efa5c2aac97a927b01000000000000009ab2927908f20efe0aa8218bb35584aab8c12ed8296d35ff52fdc09cd31ec744010000000000000044bd6d820dc0b0aa0e896331ff92a49fb13d4c9a1cba4a09cfcdeed6f9b78532010000000000000018fe1ca74d50a35d5c036bc87c52e3b0d5630f7b84ead37963388a5464e0d4490100000000000000e48535befb2e807e3d0cc182fe9e05a2d3a8bac4dec6448aae7eab5fa86956250100000000000000d81837593758987c9ef04f350de913a8a9ceaaefacb1ce4f54e27512e047d0090100000000000000724281a8cd7ff6cf4fb578ca8fc4bc9e41a3c223e10c5f11ec50acace91ef0710100000000000000d8d17c130c3b9281eb825c8020ecfb34823fdcb5b1423a4437e9ba1ff137d67701000000000000002a0cd19f595d69c11038897ee10a39bb8a05b3f254a4649bfa5a4c8b658b540a01000000000000007459633fb56901747ace72a8371eeb57c1694aaefae3e026fd5a4a288c295d160100000000000000508b057341d4a6801eff555343dcf7f427b5dc97f711f702e34074821e28d43a0100000000000000b29445ff1e828bbbf47c3f513efee4a5a23366620033ac53b8408d89a50d85170100000000000000f0306d3da4e222bda6617ba21253d8e1c59853edf60d9cfd28ca4f6dc6eacb2a010000000000000062d30d7886fc9cbb5ce8d48b9df7f5b94906f633a7cc9f6fd3000ef1257aa46501000000000000001c1420b31f9900d967e3dc211dd4a35e446362ecfbf086903ebf420a9e35b77e010000000000000018f7c58fb2cf70d650f445d474d2be3fb9e8cec4a71bccf0effc229a8784df610100000000000000f8a98c9f6100847f98bbef8ff3a4c9a7145f770cd0d127767dae18bba50bda4601000000000000002cca14fe72f6c3b74b58957566916f051a17835bfdfe68568a05f501f499f87001000000000000009ef6a9bd209d70f858a0eccdbc1cfe4e9438967cd75c395c4552b2d3ce05ab520100000000000000da5e5836d77f80d1196569fd401327b8f59d03251921d4e1740327e39e23da6b010000000000000092ee42aa9270f21adfd9f282ffb73b3e942fc06038bc83de35d8e9971fdde3080100000000000000c8f17d9b568dc9792cd03c61a1342e81afbc1dcd3f5fe54d5a85fd50ad85b6230100000000000000402a56dc42d0874e9494013cbcfa9b8a8e38f36a8a64190a3b3cbef43ac0643e01000000000000002887dcb68a706130ac662baa3021d73f5204b56aad3d87eff0ba7774f18ce54b0100000000000000182e505cd7b3d53b33aa96b6729c182c335026cfe3b16e9b02be3bae64d80d2301000000000000001cf887bde697b016fc21f0bb5cc432a02f1604d7618944bb0a8b332565db2f250100000000000000c2271f77443650b87c58eafb27ef622759ce18eba3f11d5c790d90113b5baa390100000000000000dc58296ce30054fc942543d1058bd81d994ccf73ffc4fe7af1bfaaab1ab4ab180100000000000000289ea60fad4ef35fa3a1743a46d23b7c7530ac4497002b205e6b30ed69604a290100000000000000d8eda667e774930626e2ca3fc676559fa14e5e573fb3d86addf9fcc57bb9ea32010000000000000088f9078eac35965f1868f68f8759ba030ceb7d28bd503b7df9f39fd38e87e25a0100000000000000d23382c75de4f812e29e8895c880793b4ab4fd3d28790944675499e255d4774b0100000000000000660fe756513cbcabaeed97224784cef9f128a4d4b02bb4fa004ed9663feab90f0100000000000000b60d525db7d60ac59d4ccffa3afd8a1094a080729c0d19c7b539b0191c52640101000000000000000c0bcbe2e6ff076aac9f38fed2e301c08447fafc07e085a0980beb3fab0ed526010000000000000060d29f2aa543abe8e1f102a5f106a6496f63298103d9231f730721bc62116e1c01000000000000009a883590ad1970522f048f8ead7b0e6dbb4a586bd068ace45201fada8981c8770100000000000000e6a08e00ddfe7e2158d83bce39035533eca9502c8693d44c2baa472e2ea63d4701000000000000007ee37a4d18261a43aeaa08030dada2fa2fa0b9dfbf4aa39e36973c6b1a2fdb5e010000000000000062d1095d44ec36d6b76f22ef82ac0f75505f722ea0627ae0bfe5e261f503823f010000000000000008f7dded97687143c275e5e947f1b335366a3d207c88df746a0c1c40af33b57e0100000000000000badb0b0033fb149dcb82e55a3a27ccc846c9572c13f88a8fb927e56903106971010000000000000042d092dfc6e035ea66fe617d8523dafec8d46cfe4db19ff22bab8355b483854c0100000000000000a8c0111411f9ae214c425b21a708841db2c094bae35951fe738d806d3349985f01000000000000009e4a15681b6086bede043fcd452d68426aee3afe74a9fccbca015a8d120bb207010000000000000050964893494da29e8824ab45493d956a784bdb31ec4b2311a2906c6ad1bab11e01000000000000001a24a7e0ab1ba604832e179a20f1b221d632f340749cc8a01de7aabcf098ef51010000000000000092f78f633dddc2d47a49b48a6684826c5447afea2ddf50875010298352280d050100000000000000fa26b1a6aac636a1b8d7db8b177f3f7b3915c5b287e86f06dd9015b815512b410100000000000000640077e1a2175b4d734aa5e597c1f28bc09817b9c5d3af37b24314e80da56f4d0100000000000000e007bad84209b4a4ca5110d717b98050ca2d3712b5e2795a991ff62c8e217e2901000000000000007a911bf0ab42098d8db035743bd4f7b7a5ed91a44508837da94e111357e6801c0100000000000000a8f2fa12f1bd4245d7af8390f9fcc9206aed550ff8366bf81412e2501f8b5a0701000000000000008e0b120eea5193f6731daac3fd19cd744191e250592e190797d0c9273912fa1901000000000000003af2e1ce5fc087258447fc497f472912caa1fa0702573884c6020fac3cbb74510100000000000000cafcf18b2d1f6a60bf52328011c25068f1ae49e109f4d45ca194a7acd5a8f0170100000000000000926fd4ade255b73ad4a4dd444acd2f4565deb2336223d661d9f1781d9f729426010000000000000016b71bd30e9748c8c89e5898fbf0f01e320fdd574aee590e6cac3ebaf0028c0301000000000000007640ba8105b5391aa56fef680822c673dc422b7d601c02cce4949dcece0ab85a0100000000000000f6850b653753d8a7231fe0f790f2f5fee8530743b41a2f8f4c65677a09144128010000000000000028e5fe1fc01920b017233d4debce9179791c8e60995f950756258c17f3e626150100000000000000123d59ae5331208e9810e9354ba4cb7466ca49d078e9086adb699254ca457d3b01000000000000001aa715bb53e904ba98cfe1bf2908c6ccdc952d6a584ba967e560c94d61275b640100000000000000200c96621fa81f67fb63bd35aa62b28be6d5ffafda169e126bba66332124013e0100000000000000b0df64620c6245803cc675496b3d28150fef6bc966684ca524908dd53bc6605d01000000000000004c677e26e24ecf36659011fbc9ecfb87f98e2192102ba4ec7eb2a307014c28140100000000000000c05be111614164f811b6d792a9a5defb1bd1bfb6712b8f6632719ec47ebcbe3a0100000000000000e6a82cb09111b1828030ebcba68f066cc8a2f0d7032edf102463023a571ed51201000000000000001a52989f498960edd384cdd3f52b11984912325ae290c3050886d009bf036f7b0100000000000000027e4cfaeaae32857c2595dcc4b696790a16c313b193dcc43bfe6ee593dfca650100000000000000cafdce7b198eeede8fbb8e6e44d43ea6aa5c152577a89989002968798c87b737010000000000000054d303a6f6f58c705f3bf9e03bbcfd5fc5e78d77844e0c557782da67a34fd10d0100000000000000e0e9401a2554ae5ed8623a6a0dd4dd8115b0112262cbeac006a4adfd7929ae5001000000000000001422cb0f567ee5f431a7a871f43e8602d7e0b172dd6bf2819ca2a46e6a87c9600100000000000000fc2d047eed5fa2dfb6d7117c177ef29748d93127826e79cf6824ae863dad9a440100000000000000940491aa5c11a7dceac769beea3d4a3815f995b30f995daff3c0410ff3ec4c130100000000000000ea347d23e3bf4f11047d1580444e7f6e2797cfecafa435aace72fbc5b4a51826010000000000000022fc849fcf86c5a7b583c6580597c141c56c3115298d49a0a36d6b7e441b6f52010000000000000088ba9223502f96209814c38bec4994fe2129793f357b07aca4ede940241cd95001000000000000002c016acd6877946eb48b83a1dde11059adb5044428ce43809d3216de6d92240d0100000000000000bec6ff2a5ae2411507a472960fe1297ea1f4a197d4f5346eb71b2f2327a9504801000000000000008eb5892277e6af39c9c440dfc7554ca68b7fb10eeeb9ebc33b24815de6bb90250100000000000000867404b8820644b7ad4d620d7472dbd6b5f761e924cb364651959e06b1975e57010000000000000088da7347f8d84f63a8f499d384aeb6977dd7b840b12ead209abe4c86ab9312650100000000000000f4c75a45902b4aba440cce1947300008a6fdbc6a988fb8647edf915b99fef875010000000000000040319d4152ef99f4dae00d763969f766c3f2421a1c3a213dcad56fefbfb8c35d0100000000000000e01b84f0da8be0b1807c5cb83889bbe5148a1e8a96b0aeb9b4ae15fe674588680100000000000000e6f18b461c86aa7ad1b300ac15d9611011b44c9a10f64cd2938d6bb52be03c120100000000000000be343eab7637aa2890bbf849b345f2540ceb74ebc9a3cc86888a67902bd3e91c010000000000000020089c94aa9b42573c668eb666a7049873132508a078cc3eba7bbab25704100d01000000000000001ef72e689e5d465cb2572fafe50d869d28b219d315c18d2ac9d3f6b8ec610c6e0100000000000000302d97fc1992127356b91159e3a1d9ac27d9ccc65732d124287f1fbfb8e24a7c010000000000000028114b62e53ee6aa11fc6d5cf4c70aad10dc503a0c1d801665c176a9052e354b0100000000000000787cdecf233c0f0730da3ddbcf8c5abb0fd501f3ffe2dbd252b5bb031d207c6b0100000000000000765e230454ea87385e7158e57f21dcf67b0922344ea879638e821c51f4d4a00f0100000000000000f8eb06c255edf56235fe6d613042bb2a2a21df3002a453ad34e91d5e00d6ae650100000000000000bce7396a75dc0c6686c24d42ebc720c1fe2e38b5790a60cb90d5bd0da736aa58010000000000000026c8e0e882482190ae9d58ff7c914e881024de60fd7e0f207c83357f30acaf1901000000000000006441ff8df4d2619237e696f8103737f3b003bf66267fb681f7df831021ff441a01000000000000005a8b4fe4cc6ddbb01f141c2565530bc9677e6d6baaa50ad80a529c4e9094f31e0100000000000000ce566e342591b85edcca0c57c4ed252ae80ba9e4298fdfa0c5d984a6bdc72b4401000000000000007e18f0ebfbac50554a56cc4f1bdcf70642d36393f9f0253f55518557b35bfb4c01000000000000005a6da85d909fd6baf57b8cc915cb27b6d8ec6d39247f146aa0109bd59d17d8050100000000000000e8cbd70d0678b15fdc01adcc0a1442836332f30d55d89661ae51191a09ae5a150100000000000000cae6f66d652565c27aa90259bc04296eb4962e6d8ab8b3eee1455174d52f24490100000000000000960f63edec5036689e60e56a0f2eb75f45e4ab5befb36f03b10c988d9eff6a45010000000000000052d5c76808348ba5e350ce32b94b225a7f1c577a8899811a10efe0c18316634d01000000000000000845f4903a8500c8aaae9ea4a7d6d018f4cd148e958e2f723fccebbdc982dd0601000000000000009a8551aa56ddba67f238f2870e995f5e5c14636e72815e8ef671f97ab8debd6b01000000000000008eeaba1502468d954e19f3e28e0aae48196d229a6eb5762b22ea2fe431b5a85101000000000000009c15c7a9ed43f74f816de4ba1c6d78d92bb873ede60ea4c75e9781807157524201000000000000008a0230f16fdb15c9855d211fcbd3a985819a8557ef6907cfdf0b44645e617057010000000000000058b08c94c313efda4bba48778afc50e540702b527d2d583d38f3a83ab639c85c0100000000000000f08c9a0d1e9ea3491c6bbdf94733ddcb07ad16cb75953748946c4b65c1628847010000000000000056854f0d2a2deeb5729c37d65334ad98a193fd29847f92cd7cba0935f33ee8140100000000000000f8fe5e8c5e2173894a49cabe37a9ba068c383043bf90d8694f37373c3d401a200100000000000000c2e8426228531975cb2963d57bff68b31949840187b43a43836e43a247350c470100000000000000408f0da37f48afc23014b52c52b4de90b04cb47f0021500983800f891a9ad0350100000000000000c6e0d3d715fa693dc0fec013c0d58cfeab2698b1da6472076b9ac9aecc3f70350100000000000000022cedafe7d099e6c4c6063b07daed9c9a4f85a14f01a2524a3a66710cff0d1801000000000000001074454308ee7ea6f14915f6292003376a0c4a7f7d47bfdf7c03fee7217e3f0401000000000000000215a230c8a0056fbd42596c7d707fbad5a04d2058ac6e485ea57ab97ec358740100000000000000f087ec6e70b76d03309c82b4e3721a7d23b487c29953a33f6bb1f4faa3930026010000000000000088d889391d92f6ac6d6dcc195599375b4db24e414924690166f8ad1eb7b5b6070100000000000000d86dbadd9c4938544b946d2d2ddb57a963aeb1d4a7f34566edbeab1873ca357c0100000000000000a84b258720551c4c0f1759c6c82316468000547da08a3c0326de3f9d71c353470100000000000000d0f8ecef301c4ed00db8a84c4e2849192f80428adc7703d376572b70136c755c01000000000000004c9ea41ce997ef20f010009767f91a6e71379f1676b234bdfe63c5fba2b7300c01000000000000001267d8885ac15dd9c3dc1fb28a3604963bd650ca2a99a097e0aa7b56b6a41d2c0100000000000000ec28e2aa5c3b96b46a8082a6cdee58e6b8eec81380b0fc14c9809f750b7250540100000000000000c6e1836fd76d6c06dc9b277d4b0782e8973c0655244d59f9f6712d161f15502201000000000000007a2886724b12f97d1b7ad788067b082286f3cd47f0f701f415214526da8a4d67010000000000000006b7a572d089bcd98935562a8996b4b2f6de5814887c7cad1ccf5cc2d23656330100000000000000d858bae50a7f08d01d2d73c63b5f106e04e95d0d794d361697e863254120fa5e0100000000000000040000000000000002b00b81c6b8f14d6e5de86369afb7d2b3c549cf0658d46dd5d86e57aa54b8faf4d419ad0001594a00000000000070e4a61100000000580200000000000061021004c021ae3e2d15f18d90800390836c5b30ca712d2f11c98e6ce41c3398175e010000000000000052fdf822726bb7479e08d00807b958bbc1240cc192c9b181e55437adabb5280001000000000000004e18d3de91affc85026448233d8c2cbc22d2f9ac49cbc7995e9c81b51a43cc6e010000000000000044454c1621aee6e3c1d9c3fb5026590d26be8add25900a394e24cf4138fc351001000000000000005e6f361a74bebe89866eb73d4fbe8689acfbecfaafeabc40e43236e656f22e39010000000000000094f97236c3431614d5b4deb6122dbd59eb801176dddb0ae30bce8afa1051d50b010000000000000012a9842734de09ea4847d09f1cbbbef40a44ea00733501c8933cf52442463228010000000000000010b5c4551c264f14cb2d5db4d5b50552d69534e013a062a9cb03d766df48ef4e010000000000000088435dc373ebb523a73f6d5e1496dd90627052f7e9369e38bde2581e5a9aca1f01000000000000003a54928c21dbe6d0d8ce229ae899640194f962821a4faa6a911fa8fda1809b7a01000000000000005abfafd5b636a5263653f7217d74a02711a4f13962c3f694f875f6d0cf1b357a010000000000000088ed4c0c51838ad383c7554841f76c6f623da39f948110f927bc0701a8c8960f0100000000000000c8c8cdaa16a9102f49d4fc3ed3d14e09176987c85a3cdb31406b9694996a3d0f0100000000000000eabeed18aba996fff73a024926d079fde07fc9612d4de305bf630f7bd34db7510100000000000000dada9cf6148af40fccdda9586e25c50088651f0dcfe6a9a6057dafe4d9ea8e06010000000000000068ba40acfe65dd1daa70dd5c6c3fba664e0cf005e175f0204deb1c5d1d15f454010000000000000050073bf942fbba7324a63e6c174d99240bd649bd5d0d501ca75bd259f2fa34510100000000000000c0d7173ab223397c821c26326e935363b12fe7422323a0c34178630cc2c91d3501000000000000006aa461fba284ea42deadaa35f2c255616cffc121334ee4b3303da5bad29f79340100000000000000daa36dfb87d7ba34e315b4e31388ae6cba278cdb9896c417efa5c2aac97a927b01000000000000009ab2927908f20efe0aa8218bb35584aab8c12ed8296d35ff52fdc09cd31ec744010000000000000044bd6d820dc0b0aa0e896331ff92a49fb13d4c9a1cba4a09cfcdeed6f9b78532010000000000000018fe1ca74d50a35d5c036bc87c52e3b0d5630f7b84ead37963388a5464e0d4490100000000000000e48535befb2e807e3d0cc182fe9e05a2d3a8bac4dec6448aae7eab5fa86956250100000000000000d81837593758987c9ef04f350de913a8a9ceaaefacb1ce4f54e27512e047d0090100000000000000724281a8cd7ff6cf4fb578ca8fc4bc9e41a3c223e10c5f11ec50acace91ef0710100000000000000d8d17c130c3b9281eb825c8020ecfb34823fdcb5b1423a4437e9ba1ff137d67701000000000000002a0cd19f595d69c11038897ee10a39bb8a05b3f254a4649bfa5a4c8b658b540a01000000000000007459633fb56901747ace72a8371eeb57c1694aaefae3e026fd5a4a288c295d160100000000000000508b057341d4a6801eff555343dcf7f427b5dc97f711f702e34074821e28d43a0100000000000000b29445ff1e828bbbf47c3f513efee4a5a23366620033ac53b8408d89a50d85170100000000000000f0306d3da4e222bda6617ba21253d8e1c59853edf60d9cfd28ca4f6dc6eacb2a010000000000000062d30d7886fc9cbb5ce8d48b9df7f5b94906f633a7cc9f6fd3000ef1257aa46501000000000000001c1420b31f9900d967e3dc211dd4a35e446362ecfbf086903ebf420a9e35b77e010000000000000018f7c58fb2cf70d650f445d474d2be3fb9e8cec4a71bccf0effc229a8784df610100000000000000f8a98c9f6100847f98bbef8ff3a4c9a7145f770cd0d127767dae18bba50bda4601000000000000002cca14fe72f6c3b74b58957566916f051a17835bfdfe68568a05f501f499f87001000000000000009ef6a9bd209d70f858a0eccdbc1cfe4e9438967cd75c395c4552b2d3ce05ab520100000000000000da5e5836d77f80d1196569fd401327b8f59d03251921d4e1740327e39e23da6b010000000000000092ee42aa9270f21adfd9f282ffb73b3e942fc06038bc83de35d8e9971fdde3080100000000000000c8f17d9b568dc9792cd03c61a1342e81afbc1dcd3f5fe54d5a85fd50ad85b6230100000000000000402a56dc42d0874e9494013cbcfa9b8a8e38f36a8a64190a3b3cbef43ac0643e01000000000000002887dcb68a706130ac662baa3021d73f5204b56aad3d87eff0ba7774f18ce54b0100000000000000182e505cd7b3d53b33aa96b6729c182c335026cfe3b16e9b02be3bae64d80d2301000000000000001cf887bde697b016fc21f0bb5cc432a02f1604d7618944bb0a8b332565db2f250100000000000000c2271f77443650b87c58eafb27ef622759ce18eba3f11d5c790d90113b5baa390100000000000000dc58296ce30054fc942543d1058bd81d994ccf73ffc4fe7af1bfaaab1ab4ab180100000000000000289ea60fad4ef35fa3a1743a46d23b7c7530ac4497002b205e6b30ed69604a290100000000000000d8eda667e774930626e2ca3fc676559fa14e5e573fb3d86addf9fcc57bb9ea32010000000000000088f9078eac35965f1868f68f8759ba030ceb7d28bd503b7df9f39fd38e87e25a0100000000000000d23382c75de4f812e29e8895c880793b4ab4fd3d28790944675499e255d4774b0100000000000000660fe756513cbcabaeed97224784cef9f128a4d4b02bb4fa004ed9663feab90f0100000000000000b60d525db7d60ac59d4ccffa3afd8a1094a080729c0d19c7b539b0191c52640101000000000000000c0bcbe2e6ff076aac9f38fed2e301c08447fafc07e085a0980beb3fab0ed526010000000000000060d29f2aa543abe8e1f102a5f106a6496f63298103d9231f730721bc62116e1c01000000000000009a883590ad1970522f048f8ead7b0e6dbb4a586bd068ace45201fada8981c8770100000000000000e6a08e00ddfe7e2158d83bce39035533eca9502c8693d44c2baa472e2ea63d4701000000000000007ee37a4d18261a43aeaa08030dada2fa2fa0b9dfbf4aa39e36973c6b1a2fdb5e010000000000000062d1095d44ec36d6b76f22ef82ac0f75505f722ea0627ae0bfe5e261f503823f010000000000000008f7dded97687143c275e5e947f1b335366a3d207c88df746a0c1c40af33b57e0100000000000000badb0b0033fb149dcb82e55a3a27ccc846c9572c13f88a8fb927e56903106971010000000000000042d092dfc6e035ea66fe617d8523dafec8d46cfe4db19ff22bab8355b483854c0100000000000000a8c0111411f9ae214c425b21a708841db2c094bae35951fe738d806d3349985f01000000000000009e4a15681b6086bede043fcd452d68426aee3afe74a9fccbca015a8d120bb207010000000000000050964893494da29e8824ab45493d956a784bdb31ec4b2311a2906c6ad1bab11e01000000000000001a24a7e0ab1ba604832e179a20f1b221d632f340749cc8a01de7aabcf098ef51010000000000000092f78f633dddc2d47a49b48a6684826c5447afea2ddf50875010298352280d050100000000000000fa26b1a6aac636a1b8d7db8b177f3f7b3915c5b287e86f06dd9015b815512b410100000000000000640077e1a2175b4d734aa5e597c1f28bc09817b9c5d3af37b24314e80da56f4d0100000000000000e007bad84209b4a4ca5110d717b98050ca2d3712b5e2795a991ff62c8e217e2901000000000000007a911bf0ab42098d8db035743bd4f7b7a5ed91a44508837da94e111357e6801c0100000000000000a8f2fa12f1bd4245d7af8390f9fcc9206aed550ff8366bf81412e2501f8b5a0701000000000000008e0b120eea5193f6731daac3fd19cd744191e250592e190797d0c9273912fa1901000000000000003af2e1ce5fc087258447fc497f472912caa1fa0702573884c6020fac3cbb74510100000000000000cafcf18b2d1f6a60bf52328011c25068f1ae49e109f4d45ca194a7acd5a8f0170100000000000000926fd4ade255b73ad4a4dd444acd2f4565deb2336223d661d9f1781d9f729426010000000000000016b71bd30e9748c8c89e5898fbf0f01e320fdd574aee590e6cac3ebaf0028c0301000000000000007640ba8105b5391aa56fef680822c673dc422b7d601c02cce4949dcece0ab85a0100000000000000f6850b653753d8a7231fe0f790f2f5fee8530743b41a2f8f4c65677a09144128010000000000000028e5fe1fc01920b017233d4debce9179791c8e60995f950756258c17f3e626150100000000000000123d59ae5331208e9810e9354ba4cb7466ca49d078e9086adb699254ca457d3b01000000000000001aa715bb53e904ba98cfe1bf2908c6ccdc952d6a584ba967e560c94d61275b640100000000000000200c96621fa81f67fb63bd35aa62b28be6d5ffafda169e126bba66332124013e0100000000000000b0df64620c6245803cc675496b3d28150fef6bc966684ca524908dd53bc6605d01000000000000004c677e26e24ecf36659011fbc9ecfb87f98e2192102ba4ec7eb2a307014c28140100000000000000c05be111614164f811b6d792a9a5defb1bd1bfb6712b8f6632719ec47ebcbe3a0100000000000000e6a82cb09111b1828030ebcba68f066cc8a2f0d7032edf102463023a571ed51201000000000000001a52989f498960edd384cdd3f52b11984912325ae290c3050886d009bf036f7b0100000000000000027e4cfaeaae32857c2595dcc4b696790a16c313b193dcc43bfe6ee593dfca650100000000000000cafdce7b198eeede8fbb8e6e44d43ea6aa5c152577a89989002968798c87b737010000000000000054d303a6f6f58c705f3bf9e03bbcfd5fc5e78d77844e0c557782da67a34fd10d0100000000000000e0e9401a2554ae5ed8623a6a0dd4dd8115b0112262cbeac006a4adfd7929ae5001000000000000001422cb0f567ee5f431a7a871f43e8602d7e0b172dd6bf2819ca2a46e6a87c9600100000000000000fc2d047eed5fa2dfb6d7117c177ef29748d93127826e79cf6824ae863dad9a440100000000000000940491aa5c11a7dceac769beea3d4a3815f995b30f995daff3c0410ff3ec4c130100000000000000ea347d23e3bf4f11047d1580444e7f6e2797cfecafa435aace72fbc5b4a51826010000000000000022fc849fcf86c5a7b583c6580597c141c56c3115298d49a0a36d6b7e441b6f52010000000000000088ba9223502f96209814c38bec4994fe2129793f357b07aca4ede940241cd95001000000000000002c016acd6877946eb48b83a1dde11059adb5044428ce43809d3216de6d92240d0100000000000000bec6ff2a5ae2411507a472960fe1297ea1f4a197d4f5346eb71b2f2327a9504801000000000000008eb5892277e6af39c9c440dfc7554ca68b7fb10eeeb9ebc33b24815de6bb90250100000000000000867404b8820644b7ad4d620d7472dbd6b5f761e924cb364651959e06b1975e57010000000000000088da7347f8d84f63a8f499d384aeb6977dd7b840b12ead209abe4c86ab9312650100000000000000f4c75a45902b4aba440cce1947300008a6fdbc6a988fb8647edf915b99fef875010000000000000040319d4152ef99f4dae00d763969f766c3f2421a1c3a213dcad56fefbfb8c35d0100000000000000e01b84f0da8be0b1807c5cb83889bbe5148a1e8a96b0aeb9b4ae15fe674588680100000000000000e6f18b461c86aa7ad1b300ac15d9611011b44c9a10f64cd2938d6bb52be03c120100000000000000be343eab7637aa2890bbf849b345f2540ceb74ebc9a3cc86888a67902bd3e91c010000000000000020089c94aa9b42573c668eb666a7049873132508a078cc3eba7bbab25704100d01000000000000001ef72e689e5d465cb2572fafe50d869d28b219d315c18d2ac9d3f6b8ec610c6e0100000000000000302d97fc1992127356b91159e3a1d9ac27d9ccc65732d124287f1fbfb8e24a7c010000000000000028114b62e53ee6aa11fc6d5cf4c70aad10dc503a0c1d801665c176a9052e354b0100000000000000787cdecf233c0f0730da3ddbcf8c5abb0fd501f3ffe2dbd252b5bb031d207c6b0100000000000000765e230454ea87385e7158e57f21dcf67b0922344ea879638e821c51f4d4a00f0100000000000000f8eb06c255edf56235fe6d613042bb2a2a21df3002a453ad34e91d5e00d6ae650100000000000000bce7396a75dc0c6686c24d42ebc720c1fe2e38b5790a60cb90d5bd0da736aa58010000000000000026c8e0e882482190ae9d58ff7c914e881024de60fd7e0f207c83357f30acaf1901000000000000006441ff8df4d2619237e696f8103737f3b003bf66267fb681f7df831021ff441a01000000000000005a8b4fe4cc6ddbb01f141c2565530bc9677e6d6baaa50ad80a529c4e9094f31e0100000000000000ce566e342591b85edcca0c57c4ed252ae80ba9e4298fdfa0c5d984a6bdc72b4401000000000000007e18f0ebfbac50554a56cc4f1bdcf70642d36393f9f0253f55518557b35bfb4c01000000000000005a6da85d909fd6baf57b8cc915cb27b6d8ec6d39247f146aa0109bd59d17d8050100000000000000e8cbd70d0678b15fdc01adcc0a1442836332f30d55d89661ae51191a09ae5a150100000000000000cae6f66d652565c27aa90259bc04296eb4962e6d8ab8b3eee1455174d52f24490100000000000000960f63edec5036689e60e56a0f2eb75f45e4ab5befb36f03b10c988d9eff6a45010000000000000052d5c76808348ba5e350ce32b94b225a7f1c577a8899811a10efe0c18316634d01000000000000000845f4903a8500c8aaae9ea4a7d6d018f4cd148e958e2f723fccebbdc982dd0601000000000000009a8551aa56ddba67f238f2870e995f5e5c14636e72815e8ef671f97ab8debd6b01000000000000008eeaba1502468d954e19f3e28e0aae48196d229a6eb5762b22ea2fe431b5a85101000000000000009c15c7a9ed43f74f816de4ba1c6d78d92bb873ede60ea4c75e9781807157524201000000000000008a0230f16fdb15c9855d211fcbd3a985819a8557ef6907cfdf0b44645e617057010000000000000058b08c94c313efda4bba48778afc50e540702b527d2d583d38f3a83ab639c85c0100000000000000f08c9a0d1e9ea3491c6bbdf94733ddcb07ad16cb75953748946c4b65c1628847010000000000000056854f0d2a2deeb5729c37d65334ad98a193fd29847f92cd7cba0935f33ee8140100000000000000f8fe5e8c5e2173894a49cabe37a9ba068c383043bf90d8694f37373c3d401a200100000000000000c2e8426228531975cb2963d57bff68b31949840187b43a43836e43a247350c470100000000000000408f0da37f48afc23014b52c52b4de90b04cb47f0021500983800f891a9ad0350100000000000000c6e0d3d715fa693dc0fec013c0d58cfeab2698b1da6472076b9ac9aecc3f70350100000000000000022cedafe7d099e6c4c6063b07daed9c9a4f85a14f01a2524a3a66710cff0d1801000000000000001074454308ee7ea6f14915f6292003376a0c4a7f7d47bfdf7c03fee7217e3f0401000000000000000215a230c8a0056fbd42596c7d707fbad5a04d2058ac6e485ea57ab97ec358740100000000000000f087ec6e70b76d03309c82b4e3721a7d23b487c29953a33f6bb1f4faa3930026010000000000000088d889391d92f6ac6d6dcc195599375b4db24e414924690166f8ad1eb7b5b6070100000000000000d86dbadd9c4938544b946d2d2ddb57a963aeb1d4a7f34566edbeab1873ca357c0100000000000000a84b258720551c4c0f1759c6c82316468000547da08a3c0326de3f9d71c353470100000000000000d0f8ecef301c4ed00db8a84c4e2849192f80428adc7703d376572b70136c755c01000000000000004c9ea41ce997ef20f010009767f91a6e71379f1676b234bdfe63c5fba2b7300c01000000000000001267d8885ac15dd9c3dc1fb28a3604963bd650ca2a99a097e0aa7b56b6a41d2c0100000000000000ec28e2aa5c3b96b46a8082a6cdee58e6b8eec81380b0fc14c9809f750b7250540100000000000000c6e1836fd76d6c06dc9b277d4b0782e8973c0655244d59f9f6712d161f15502201000000000000007a2886724b12f97d1b7ad788067b082286f3cd47f0f701f415214526da8a4d67010000000000000006b7a572d089bcd98935562a8996b4b2f6de5814887c7cad1ccf5cc2d23656330100000000000000b781c437c36c5023d5c22fb3549e5e093f356c4d31e8b1df67a3fef0025181cc0100000000000000040000000000000002","babeFinalizedBlockWeight":2834100,"finalizedBlockHeader":"0xc38c4d9e6390a9adc1855f2d3b6a54e9465b1cc44cafcedda755941708533d562e78b4020ad9e09ccbb981764115103045c8a3289f4bedcd05da6d613a2b47cf5d1debcb398612078563281c13b4c1bc5e22469014ec5dea7eea535a0abbd72cf07c66c30c0642414245b501038a00000055e6a611000000001e2d3e07fd79319b1806a29fb51cec748535f456e7cbf4d575b5e3e8d780306ecb7dd6520c88580901a0f329b6fc4bfff092f73065d866efa02941075d18810063a4f7a984317ffa9ad1c6be9e2a518bdd97417731976f90817aa22d99d8760f04424545468403f9011e3de183d1854d23815e7b9a4d49cdef4388db13f60e634fbb36cd4590ec05424142450101f60178b52c23e86c95001f919eb24931f8709765a315445a7b5f52f49e36855d2c322c1fa67aa90a05d802d1bf71def6335dc91185d3aa342b4100568767618b","grandpaAuthoritySet":"0x6102ad4be70209b5c84f0d9a26d1b476570226c6237af41bd89566fdb809e99a66b80100000000000000384c6ec2c8511c9454f75b9f59a30a689cbb2ec34037027d2f3acecbeb8abe7501000000000000006b4f558832a86f088340ca1ebabe2147823036c6abbde36e4e0be8dcd5c18f2901000000000000003fa770bbcfbdaac57d9923d9d854e98d76c1d59f5ca976adca479e6850324b0801000000000000009b2df55c2234271d1c9d5cd4291301ec624ee6d13bddf78dd8fc87c31d42fc9a010000000000000084beadb089cda5e0bc20bbc08665499c4d641bb189864f58e40a71a12b1bd776010000000000000076ed6307d6470b4c239398b36937b4e3ca0fb8157daff442c33442eefe2814c801000000000000004aa5708f54845419489cb1aae67c20048d062dfa9ea0b9a84de18376f4ce92c60100000000000000557b8639195aafda6700108fb6df0cc47253f8cde2d2b6113bed282e277c614d0100000000000000823557a6b0d397bcd3d188aa06fc13e49c636cbdbf35eccafd2d23595e6589e20100000000000000381e3981cbe65010b643ea621caacfee6f4c5a30daaec93b45fced51e7ddb1bf0100000000000000f30a20b577ee6bea8a45b9cf9ad8417d0078f7786acf7b185260f4b651631ed10100000000000000d2de0dd5eba4dd3505620952979e4c5b34f47d1cd5c345d6910aa732166c4ba301000000000000008560ab563f2a3d9b84de611fcfc2feda7a6b19bf5e61cf20f9fc63bc138de4e20100000000000000af69664425d84ef811ffae3cbe6ac6fbe2063c18ac74a655c2cbd6c4339987720100000000000000d23c267273b83bb35c8e838a2b6c47c49af8f15bb03b4ebbc0c171c0242cb15c0100000000000000f5b183712646f29d40c2c0877305cb0d902c214f0a8d44c5b8e6c765384740b401000000000000002bf2597eda54f89e851d398be9c76ed7d8a43948ca60418c7361ec5bce6ab50e01000000000000002f6469ed6f2f9e92cb3126281c8bf6ff65e77f205681b37aa7b9c909b168e201010000000000000021214b3c44747d87d048d179c55dacba3c02e74ce77e0b845bef3444952cfac3010000000000000099ab99a1b0af27bd480542c88b35a6624ef2121de54329582811aa9b31a3c733010000000000000022d04ea680d28674d9809c2022b38e60911447657af9abe8eaedac409ae2deff0100000000000000c3af638f3ed2948314eb771e970f73eceac5a2e101ea327fa8fe43e9ee61dda80100000000000000719ea231a2dada4838ddfd19beb3ac7ce248b660b1fccda83b033d0865d2aa4701000000000000008a75b8423c11621120418655cb54f75f0eb73984942e0344bc47071c693be638010000000000000045b06ca79993b20a511dda12cbbccd2c43d1e1eb827ecf826620286045cf4fcb01000000000000006902cc7446affffa43ea696c8c0507a3c38ca0faf868544a7ab3d573019f3ca201000000000000001cdd699d681de6c6ef2405e25743a09b2d94548c840d0f1c9cdc5f4e0a856efb0100000000000000f37f2770340536125bd480d788caaf45fdfc650ea29a7331d1fadd9f7f6ddd2f01000000000000006efdc9c518967ad4229eb48e549fec8263aec4a1910326235118e36335a6fc1701000000000000005eec271b03fcf4277015ab182472fc55719511240c8bef8545195312d351f7750100000000000000fbe0ff885839b919968749b6a04960d235981e9493b14b8e1cd1c852d853e5e8010000000000000004bba58e0b53f515010d1cc56453c10c9333b8fa5ffc1412621dec556860b8e4010000000000000059750fcc03242d2eca42f5827074f93442ce3fe47f03847dd0dead3e29eb0e820100000000000000dbdb0b2b2e7b20de3f85dc61ab8fe9320c6d1e8c7323f60d08a909a31cf2ffed0100000000000000b9ff0e4f0c0c881ae6c3546ed350391c6379649506582a4d16868eca1bc9a5250100000000000000eff1d5c906c90c1b482280619cabc0762d3dcb793cdbf6e26d5b00d14cda38c90100000000000000c80773fbc6144ba0c3d5b35a6e46f6ca2998513d172a8818bce2445f3365b6e901000000000000006a9e0c68b5f539f69cb01be910e5d5a299bb2a50b3b9b0a033a69416ed57b15b0100000000000000c6c9d24ef1f3bf0fa99aa5f0bafe680d8ef42e421398bcbe691f7bcb617bfa140100000000000000400ba8e74fc9470b03de528e828b8741e9455f4718eccfc2ea04499bd321f62901000000000000004b38d9dad38539affc9f0fd20471e6a3f55752fc9d7cd0dc035a681c2eab81f501000000000000005e40741a21cdd46e7071174f3801f07b492b0941540454835383f46396849b3e01000000000000008b4f995dbf0667e2c14342cbd141e3a87bd1c3dac56c4faaf3736a1e826ba69601000000000000007e939b0b53123a2e357da8247e659430c310feeb89836d2224e0a65bda419c2d0100000000000000d84ecc11b2fcc511e3799db37695cd9f1bea7e581f7f259c5f70ad6e41d74b0d01000000000000004d5e8f0e248bf0ebdf8c708b1d61e475162bcedded321222528402f10811eabd01000000000000007251da7147c58d25365bc6245897d11a6656be57fa3f9ad095746d3d2556e86201000000000000003d105567dd34373f70e746243abfd00b0eede0e36f2836ec7a8ac95c26acc9fa01000000000000003bc2c8a405f7acd25a5e6c1cee72ce8e2c247b05c6b37b9c2bf7654a624e0b4601000000000000004fbba25109fe9ee6e27322db51f40c56ad8940fa9388de42ca43bcc9f2c221a0010000000000000036b8caeb2b1963addacda511e61f005e298bb7aa2a6c67a25c0d8c49bf46640901000000000000007919f465947953a6d82378e215561a6214d4b14b7bf519c2204cdc2079a2226c0100000000000000ea15fedde78eebcf26a7646dc5489518bb1c3d21598591483cded88fab68c0470100000000000000bb621691b37e0a1e5006f09c9c43e7ba19a43edeaa383654c8576efdaa01245401000000000000003a960631477eaa37fbeea5016d0887240207e86a05537d39e61d6250e2238d340100000000000000e0d591aa1c2d5fe4ca6372e3f305c529095dac8beaf140296f9ccf4696223f5f010000000000000088554c14ccee6a400aeed70df6b44541e301db7546b72071439658cbc8b6d31c01000000000000009f263ae8823a331e0dfd297da617def759acc03cf409767cbd8edcc79f4f3f580100000000000000f09a897aa6df3fc451bbfa12d072b6805145adbcc50ced15f6227a9d5d145b17010000000000000046fcf86f16a521650a94722efd176b0c9467fb48cbe3e9c40fae86ea27322e110100000000000000c9ee90f0b392fd960e207febc29146a8e045a955c8c06588d9d2e1080751b59e0100000000000000be73d3cad71c400291d1f1ef1d3f25b5924f5efa2d509e62389106d5d28eab8b010000000000000078e0a781a72114b2ea5689cadf5401c8122b04f799985c3ea3c41c936981cdc40100000000000000b86e7a22d2cc7ff75e1a204b5f0b6d8a660e067e100755da2375e73d28ade4060100000000000000e2f4ca20d0305eb14715e8fa015b7125487490d3b2ddda90e4a3b3ace8b44a68010000000000000006081b1806b53c3dbc7a2fc3c5932f3dd3dbcde6e3cc865a39913f5e6352c4b90100000000000000a23c14809e06b77499b621c7528676377f2561a8ca43f5ec2ab86aba1929445101000000000000003468a2de1b18afd7978a2dedeef06d46651ab473181d0e063374d12e026dcf2c010000000000000060d1fe50e6f42402921500bcc8ef367e160c51d2f63fe6b1e81c703910320c1d010000000000000056db972358d8170db0515ad31c26a0e9706c566d9180bc7034863bc81accf9290100000000000000c9da738b1fe72e00496e884864a0b9d521ea981cab204291809de08462a263cb0100000000000000fa93ba85b41840cfdb6e7fc74ae3a01210485205532688f1b906e2d7abe671340100000000000000ece9eb501e7c78a616f8513338d51ad757996feefdfaae112e22941eddcd054e010000000000000063d5731d9060be63d79b0257f9b03e677cc2e831d6b00997320d9281b12712ec0100000000000000bde44832d1d09b2dcaa74de928b9776f407dfdadbd52d28186cb5915d5d43c1101000000000000002c87d973cebba378cf91c1cb2b95ec4dc8bf8971daa6fb53528e31f46fcad6fa010000000000000048504abf30e2e4edeb0fa806f5c68a343c5351ba3b2c1221900af24a22b87b860100000000000000485d0afb31b273587c30ef16e9b2d085fd4d54f5735f50e678a71915f69717c70100000000000000705203fab555c0993842e562824ae52bee09dac6d79a47391588263eb2119b7d01000000000000006b65130f5d57f462fd3bb571406ea82f3851e95803e612d798aae10fbfb679dd01000000000000006cd9a2a780fd1600a86a28687562f57d01f7351ca4eef324a47cd694cb2c12ab010000000000000018c2337721ae8ea495e78e0de50c80810f5164ba9f37fbab7db8046c5a43c16e0100000000000000730b398581b2627e17fc1859f2b25a01d00cd10a0fad10bda7b5ece5a677cef9010000000000000070a8f2c854fa3a17a68746cfe493beb9bff8f34bbcd83d4a1dac92a82a6439260100000000000000da407968218979b6cf9f3b273d5da9d6fed41de48f203d94d85d73bc3c3775350100000000000000b6330a5333954bf601044454fb9bec3401ed59daad584bf62dd6141bdbe33bc30100000000000000727d484a95bb237b70c66aad2fdc6906595ae24784d4a4cbaa048e747cb5cfd001000000000000007a9c41976043382288ecade1687ff78cdbf892bae9f4886606fc0ed59add6c2c0100000000000000af732cc90dd5fff6d8f554af6a5eb13c0b300f4232ff516ab849f19c88d74ba50100000000000000a6eeea7cd93e70197d91d68e62c151023ac3eef1beb38af9af359634585df6c501000000000000009cb6f96ecc63671d4d40544cf69cbde1bf2f5d679ae1fcece0a256c7c9e8f1710100000000000000da550603ca4b3442d7d6ef7526b259c9290a317d2495d6cbb476279f18a42836010000000000000050cfc7356353c0fa42aa8b43ac5d2ec0f6eeb5095ef9a5a6abc0c8b8ec2963c001000000000000007eef4e31aaa7f605a0e924e1ffdaee0c1dfcc0685e7a7c5739006ba602def8cb01000000000000006fb539e77dc8f11e27f51ffbbe29a22f9c65886cc350a180cc15df1fd33739740100000000000000b18cbeac96b0399f881de179ce8dff877a85a9c88844c5ec4cff5b1ae0c65cb30100000000000000eeb7717a1ee2ebd5854f3426d8f276651693403000fa939f4073e4474b75e0100100000000000000a753a68985d94a0771b704364571c810cffab16a8fd450b4d79744b0fe89f4e90100000000000000f1e80cc588d39ad6464d5e890a704d5e9fc07920903b08f06b4b3a2ad218595e01000000000000009b0dd401864ea25913c2d5987428a3aaf33c100465843198d72e37048febcaec0100000000000000991b8f0c7c42ef75dfb24d1df053dbe18c4b2bafdd86803d2b2505a688bf7f71010000000000000019b84719ba987dd028e1fcf512a0ee857a1b53285161f586b456547f86f7fc7d0100000000000000382b9ac9a4895a6508b935b3014345a637745bd58b3335d54a1e14215b5d178e0100000000000000c0732b159411a4abdb0c29029c1ec2013fead26e03fe87b5f5ec4366435db85b010000000000000006fc73229bea563d9d98f48b4ef575dbb5570ef338798255fbb3da68bc4fff6401000000000000004a56d18eebb59c1d0480a8d0d60117693888aab705c481d4c22545e9312749b30100000000000000bddf5cbdced272ebcfa5187c44ebbc22a614a840ca015a50f489ec373866d5f30100000000000000035410457a842370fc973b116be0ceabfa81f8e1b3bb162922787bfc95f1a39d0100000000000000c8af78597e7250805b1f906518e1573e92f1740ddf5afea5a655625f8de7e39c01000000000000001eda33b98265b1141eb06d7079c7cb51419dc2170dbc998cb9628fe26b340ce801000000000000009cc46f39c94d6dde4865a254f9c8eb3138c4b3576ffbf7e62f790b531038ef780100000000000000ea316f1bface500d1f76d20400396f2d3ef4a7b18a4353133cf821b9a1148a560100000000000000c3c72e825de631fd2deb715a2170a70580431b11666639d6d09f215fd587cb7701000000000000007d73f6b78728b31063d40af90af3e6d224d71d9ab362f32da11ea255337963a9010000000000000075e1574177fdbb8863b5b247b2d3136033df4d10f9a775ce3af2123fc97dbfed010000000000000099506d8a67e458bff8ceb93cb4d77f23957138399f11fc44996e61b1df1848ea010000000000000078b5370952428e4882c764e2e14a8217710c61b03244c5dd10c9a2f3f5c05c250100000000000000d23988121a61813005168196899b929406d4f9ee0a523f3759e6e477b89261cd01000000000000004beaea3024e4c9c573439e9a087a6227c46c5bf052b7a28c296f05e7b3d8d72e0100000000000000e1cd12e7985adf9a9a5328972f8f66d035fe9ed9a50d6e01a5970146eb4cd6c20100000000000000bf47ea951bf20303e3ace58a8e95b79e1430c86b7ce97cc14b09f643e57192b201000000000000003fdbfac7e80374c510aaad223d447a49b76196c9a60e88d726d2c91760fffaaa0100000000000000cd9adcc8236338070016de846f0026051e7ae07f91f7cf39574a7262cd230728010000000000000038cff2dd11deb5999ed2055d98fd436b4858a01a43287f286dd6c932102638cd0100000000000000e87af73a4a8195b80e2c977edca55275622a0fbca48e6dd93b7c0db3ed54d427010000000000000014fa616d5b1d2f5403eee710709323ce2473f08d77b9e00d20ed517e52f4d9bc01000000000000005873f921ecc605ae9f4a98a5b59505a652fd643bb47ff66ab7cc096e957dea400100000000000000814193cc0520ea91284ed44e13979044c91771c9f508255bdd33f1c5d19183dc0100000000000000c77400bbd98289422195bf6bf8b0b57911002f77dcfe82d7e4e74f38a5e409b601000000000000000ee3e9057a71d6c6501548b6e3785802de52b8a5e87d9b9e521eea2b05918b2f0100000000000000d2096c995e2bfdf52a7c60fe4db0d64bb64d9fc9e35df3bec5e7c6c837fe45a3010000000000000064fa44e74901bd0498d7f005e757c8269cbad75323d6c678fad732205c184e5c0100000000000000cec685cdd056218ba7e245c98fdc103ae6c4606164336c51ce8ea9092f7972bd0100000000000000235b304ea70c53f060701ecb4257e03d7c1bdac282d940865534533f272c95ec010000000000000015dac847cdd7eb29e0ca6dc9e9586c5cfc6cda407692459413dd41de69589dc40100000000000000db257ed1026021a81ef5b1848dfe8588439156e20a177a9ce778973f3548d2430100000000000000d4fe9c70a8c767339e6c8f7b29ff32ed8f89c3655ee5e27df217ff2fb0f2304e010000000000000050c1a5d1e221782ddbdcaf039dc23d39691b773a9ca543305f0809ce5b31fefc0100000000000000e5b4449610d8c33729d096216500ad634fe3654420f70b7fbaeb746ff8ba973a01000000000000000eb6a7b2b5758eef2088dc65b067d281ead3252f09062d0909f5b4bdec6be88e0100000000000000f5bf306dee98b6057c9cd931f5f1c9c9d36eb591ace063ec8bb3b1150a31f2e5010000000000000081b637606f92c6d01c8c7e11e6face0a289f008f568b42c3a47d2111a95f57c301000000000000001c14c0ce66bf74a93290ed75209d56dc729890c70fba1ec6c23ff9f12daf4de101000000000000002fb5594b7750ee0cafdde385912ea201ae53162aa66ee5588c0360391773f88801000000000000001339c2c7ee10e5ca4119113db8c140c250e41e93b22927c54bc5075318c6c1e8010000000000000011cbb6a0ff372cd7ea7a9966682445e6a8c1aaebf69a562c26a97bf8d0eaffd50100000000000000f2881bc07407ab75cca9e3c1a93174b8a97f69362d39d35c755a1b426f131434010000000000000015a4d7b82bacbdbcec37aa687061702d65fd490d75562dda60db8da46961231401000000000000009c07a4b0db05b434a94e31377d254bc3c797b79212eb7e447b4f12a7c1adb12c010000000000000051d8caf8c6a388dc49cae5fe9be4f04dd889893057a011810aa0a1149a0debf10100000000000000cb4e9c0fd534185b7c365e954d670bb1fdf6fcd70e965c4caca59e262ded55b20100000000000000640c00000000000000010b1ead0000913100000000000000005909000001000000000000008b15000002000000000000000f2300000300000000000000c72e000004000000000000001f31000005000000000000002f3f000006000000000000003f4d000007000000000000004f5b000008000000000000005f69000009000000000000006f7700000a000000000000007f8500000b000000000000008f9300000c000000000000009fa100000d00000000000000afaf00000e00000000000000b9bd00000f00000000000000c9cb00001000000000000000bfd900001100000000000000cfe700001200000000000000dff500001300000000000000e90301001400000000000000f91101001500000000000000092001001600000000000000192e01001700000000000000273c01001800000000000000374a01001900000000000000475801001a00000000000000576601001b00000000000000677401001c00000000000000778201001d00000000000000869001001e000000000000003a9e01001f0000000000000049ac0100200000000000000057ba0100210000000000000067c80100220000000000000077d60100230000000000000085e40100240000000000000095f201002500000000000000a50002002600000000000000910e02002700000000000000471c02002800000000000000452a02002900000000000000393802002a00000000000000184602002b00000000000000255402002c000000000000000c6202002d00000000000000c56f02002e000000000000003e7d02002f000000000000000d8b02003000000000000000ed9802003100000000000000d8a602003200000000000000cfb40200330000000000000083c2020034000000000000005fd00200350000000000000058de0200360000000000000057ec0200370000000000000067fa020038000000000000006a0803003900000000000000f81503003a000000000000009e2303003b00000000000000823103003c00000000000000903f03003d00000000000000a04d03003e00000000000000b05b03003f00000000000000be6903004000000000000000cd7703004100000000000000dd8503004200000000000000ed9303004300000000000000fda10300440000000000000055a403004500000000000000fcaf0300460000000000000005be030047000000000000000bcc030048000000000000000eda0300490000000000000015e803004a0000000000000025f603004b00000000000000330404004c00000000000000421204004d00000000000000512004004e00000000000000612e04004f00000000000000713c040050000000000000007d4a040051000000000000008c58040052000000000000009c6604005300000000000000ac7404005400000000000000b78204005500000000000000c69004005600000000000000d69e04005700000000000000e6ac04005800000000000000f6ba0400590000000000000006c904005a0000000000000016d704005b0000000000000024e504005c0000000000000034f304005d00000000000000420105005e00000000000000520f05005f00000000000000301d05006000000000000000312b050061000000000000004139050062000000000000005047050063000000000000006055050064000000000000006f63050065000000000000007e71050066000000000000008d7f050067000000000000009d8d05006800000000000000ab9b05006900000000000000bba905006a00000000000000cbb705006b00000000000000dbc505006c00000000000000e6d305006d00000000000000f6e105006e0000000000000006f005006f0000000000000016fe05007000000000000000260c06007100000000000000361a060072000000000000003028060073000000000000003e36060074000000000000004d44060075000000000000005c5206007600000000000000486006007700000000000000566e060078000000000000005e7506007900000000000000657c06007a00000000000000738a06007b00000000000000839806007c0000000000000092a606007d00000000000000a2b406007e00000000000000b2c206007f00000000000000c2d006008000000000000000d2de0600810000000000000032e806008200000000000000e2ec06008300000000000000f2fa06008400000000000000020907008500000000000000121707008600000000000000212507008700000000000000313307008800000000000000893507008900000000000000414107008a00000000000000514f07008b00000000000000615d07008c00000000000000716b07008d000000000000007f7907008e000000000000008f8707008f000000000000009f9507009000000000000000afa307009100000000000000bfb107009200000000000000cfbf07009300000000000000dfcd07009400000000000000efdb07009500000000000000ffe9070096000000000000000ff8070097000000000000001f06080098000000000000002f14080099000000000000003f2208009a000000000000004f3008009b000000000000005f3e08009c000000000000004a4c08009d00000000000000315a08009e00000000000000da6708009f00000000000000b1750800a00000000000000022830800a100000000000000c9900800a2000000000000005b9e0800a300000000000000eeab0800a4000000000000008bb90800a50000000000000016c70800a600000000000000acd40800a70000000000000035e20800a800000000000000ccef0800a90000000000000059fd0800aa00000000000000ea0a0900ab0000000000000078180900ac000000000000000b260900ad00000000000000a0330900ae000000000000002f410900af00000000000000be4e0900b000000000000000535c0900b100000000000000df690900b20000000000000064770900b300000000000000e8840900b40000000000000073920900b500000000000000fd9f0900b6000000000000009cad0900b70000000000000037bb0900b800000000000000cbc80900b90000000000000062d60900ba00000000000000e8e30900bb000000000000007df10900bc00000000000000edfe0900bd00000000000000870c0a00be00000000000000141a0a00bf00000000000000ac270a00c0000000000000003d350a00c100000000000000c4420a00c20000000000000050500a00c300000000000000de5d0a00c4000000000000007f6b0a00c50000000000000016790a00c600000000000000a3860a00c7000000000000002b940a00c800000000000000faa10a00c9000000000000008eaf0a00ca0000000000000061bd0a00cb0000000000000041cb0a00cc0000000000000017d90a00cd00000000000000e7e60a00ce00000000000000b9f40a00cf000000000000008a020b00d00000000000000098100b00d100000000000000a81e0b00d200000000000000b82c0b00d300000000000000be3a0b00d400000000000000cb480b00d500000000000000db560b00d600000000000000eb640b00d700000000000000fb720b00d8000000000000000b810b00d9000000000000001b8f0b00da000000000000002b9d0b00db0000000000000039ab0b00dc0000000000000049b90b00dd0000000000000059c70b00de0000000000000069d50b00df0000000000000079e30b00e00000000000000089f10b00e10000000000000099ff0b00e200000000000000a90d0c00e30000000000000059120c00e400000000000000b1140c00e50000000000000009170c00e600000000000000b91b0c00e700000000000000111e0c00e80000000000000019250c00e900000000000000c9290c00ea0000000000000081350c00eb00000000000000d9370c00ec00000000000000e9450c00ed00000000000000994a0c00ee00000000000000f8530c00ef0000000000000050560c00f00000000000000008620c00f10000000000000018700c00f200000000000000287e0c00f300000000000000388c0c00f400000000000000439a0c00f50000000000000052a80c00f60000000000000060b60c00f700000000000000acb80c00f800000000000000b3bf0c00f90000000000000063c40c00fa00000000000000c3cd0c00fb0000000000000073d20c00fc0000000000000073e00c00fd000000000000004dee0c00fe000000000000005bfc0c00ff00000000000000580a0d000001000000000000b00c0d00010100000000000068180d00020100000000000076260d00030100000000000084340d00040100000000000094420d000501000000000000a4500d000601000000000000b35e0d000701000000000000c36c0d000801000000000000d37a0d000901000000000000e3880d000a01000000000000f3960d000b0100000000000003a50d000c0100000000000013b30d000d0100000000000023c10d000e0100000000000032cf0d000f0100000000000041dd0d00100100000000000051eb0d00110100000000000061f90d00120100000000000070070e0013010000000000007e150e001401000000000000de1e0e0015010000000000008e230e0016010000000000009e310e001701000000000000ac3f0e001801000000000000bc4d0e001901000000000000cc5b0e001a01000000000000dc690e001b01000000000000e9770e001c01000000000000f1850e001d0100000000000001940e001e010000000000000fa20e001f0100000000000012b00e00200100000000000022be0e00210100000000000032cc0e00220100000000000042da0e00230100000000000052e80e00240100000000000062f60e00250100000000000072040f00260100000000000081120f00270100000000000091200f002801000000000000a12e0f002901000000000000843c0f002a01000000000000944a0f002b01000000000000ec4c0f002c01000000000000a1580f002d01000000000000b1660f002e01000000000000b5740f002f01000000000000c5820f003001000000000000d5900f003101000000000000e59e0f003201000000000000f5ac0f003301000000000000e2ba0f003401000000000000b8c80f003501000000000000c8d60f003601000000000000d7e40f003701000000000000dfeb0f003801000000000000e7f20f003901000000000000f70010003a01000000000000f80710003b01000000000000000f10003c01000000000000101d10003d01000000000000202b10003e01000000000000303910003f010000000000004047100040010000000000005055100041010000000000006063100042010000000000005d7110004301000000000000607f10004401000000000000708d10004501000000000000809b1000460100000000000090a9100047010000000000009eb710004801000000000000aec510004901000000000000bed310004a01000000000000c9e110004b01000000000000a1ef10004c0100000000000093fd10004d01000000000000760b11004e01000000000000811911004f010000000000006d27110050010000000000007d3511005101000000000000894311005201000000000000995111005301000000000000865f11005401000000000000636d11005501000000000000537b1100560100000000000063891100570100000000000072971100580100000000000082a51100590100000000000092b311005a01000000000000a2c111005b01000000000000b2cf11005c01000000000000c2dd11005d01000000000000d2eb11005e01000000000000e2f911005f01000000000000f207120060010000000000000216120061010000000000000b24120062010000000000001b32120063010000000000002b40120064010000000000003b4e120065010000000000004b5c120066010000000000005b6a120067010000000000006b78120068010000000000007a86120069010000000000008a9412006a010000000000009aa212006b01000000000000aab012006c01000000000000babe12006d01000000000000c9cc12006e01000000000000d9da12006f01000000000000e9e812007001000000000000f9f612007101000000000000090513007201000000000000191313007301000000000000282113007401000000000000382f13007501000000000000483d13007601000000000000584b13007701000000000000685913007801000000000000786713007901000000000000887513007a01000000000000988313007b01000000000000a89113007c01000000000000b69f13007d01000000000000c6ad13007e01000000000000d5bb13007f01000000000000e5c913008001000000000000f5d71300810100000000000005e61300820100000000000015f413008301000000000000230214008401000000000000331014008501000000000000291e14008601000000000000372c14008701000000000000473a14008801000000000000554814008901000000000000655614008a01000000000000756414008b010000000000007c6b14008c01000000000000c56d14008d010000000000001d7014008e01000000000000757214008f01000000000000858014009001000000000000958e14009101000000000000a59c14009201000000000000b4aa14009301000000000000c0b814009401000000000000d0c614009501000000000000e0d414009601000000000000f0e21400970100000000000000f11400980100000000000010ff14009901000000000000200d15009a010000000000002f1b15009b010000000000003f2915009c010000000000004f3715009d010000000000005f4515009e01000000000000175115009f010000000000006f531500a0010000000000007f611500a1010000000000008f6f1500a2010000000000009f7d1500a301000000000000ae8b1500a401000000000000b8991500a501000000000000c8a71500a601000000000000d8b51500a701000000000000e8c31500a801000000000000f8d11500a90100000000000008e01500aa01000000000000c0eb1500ab0100000000000018ee1500ac01000000000000eefb1500ad01000000000000b9091600ae010000000000007c171600af0100000000000043251600b00100000000000021331600b101000000000000583c1600b201000000000000f3401600b301000000000000bb4e1600b4010000000000007f5c1600b501000000000000496a1600b6010000000000001d781600b701000000000000e3851600b801000000000000ba931600b9010000000000009aa11600ba010000000000006daf1600bb010000000000004abd1600bc0100000000000021cb1600bd01000000000000f3d81600be01000000000000b9e61600bf010000000000008cf41600c001000000000000c8fd1600c10100000000000031021700c2010000000000006c0f1700c301000000000000bc1c1700c401000000000000fe291700c50100000000000051371700c601000000000000a7441700c701000000000000e7511700c8010000000000004d5f1700c901000000000000ba6c1700ca01000000000000867a1700cb0100000000000084881700cc0100000000000073961700cd0100000000000043a41700ce0100000000000048b21700cf0100000000000058c01700d00100000000000068ce1700d10100000000000078dc1700d20100000000000088ea1700d30100000000000098f81700d401000000000000a8061800d501000000000000b8141800d601000000000000c8221800d701000000000000d8301800d801000000000000e83e1800d901000000000000f84c1800da01000000000000085b1800db0100000000000018691800dc0100000000000028771800dd0100000000000038851800de0100000000000048931800df0100000000000058a11800e00100000000000068af1800e10100000000000078bd1800e20100000000000088cb1800e30100000000000097d91800e4010000000000008be71800e50100000000000056f51800e60100000000000034031900e7010000000000001c111900e801000000000000141f1900e901000000000000242d1900ea01000000000000333b1900eb0100000000000042491900ec0100000000000052571900ed0100000000000061651900ee010000000000006b731900ef0100000000000074811900f001000000000000198f1900f101000000000000bb9c1900f20100000000000066aa1900f30100000000000011b81900f401000000000000a7c51900f5010000000000004dd31900f6010000000000008bde1900f701000000000000c9e01900f801000000000000cde91900f9010000000000004dee1900fa01000000000000e2fb1900fb0100000000000070091a00fc01000000000000fb161a00fd0100000000000093241a00fe0100000000000028321a00ff01000000000000b63f1a000002000000000000414d1a000102000000000000d35a1a0002020000000000005b681a000302000000000000e9751a00040200000000000078831a000502000000000000e6901a000602000000000000639e1a000702000000000000efab1a0008020000000000007bb91a0009020000000000000cc71a000a02000000000000a9d41a000b0200000000000029e21a000c0200000000000098ee1a000d020000000000009efa1a000e02000000000000f1061b000f020000000000004d131b001002000000000000771f1b001102000000000000a72b1b001202000000000000ce371b001302000000000000f0431b00140200000000000026501b001502000000000000415c1b0016020000000000008e681b001702000000000000bd741b001802000000000000e9801b001902000000000000488d1b001a02000000000000a3991b001b020000000000000fa61b001c020000000000004fb21b001d0200000000000060bf1b001e0200000000000029cd1b001f02000000000000f4da1b002002000000000000bce81b00210200000000000057f61b002202000000000000dd031c002302000000000000320f1c00240200000000000074111c002502000000000000291f1c002602000000000000cf2c1c0027020000000000007e3a1c00280200000000000027481c002902000000000000db551c002a0200000000000091631c002b020000000000003a711c002c02000000000000fa7e1c002d02000000000000a58c1c002e020000000000005d9a1c002f020000000000000da81c003002000000000000d7b51c00310200000000000087c31c00320200000000000030d11c003302000000000000ddde1c00340200000000000088ec1c00350200000000000033fa1c003602000000000000cbfe1c003702000000000000fd071d003802000000000000c8151d0039020000000000009c231d003a0200000000000053311d003b02000000000000233f1d003c02000000000000eb4c1d003d02000000000000bc5a1d003e0200000000000095681d003f0200000000000069761d0040020000000000003f841d00410200000000000016921d004202000000000000c89f1d0043020000000000008ead1d00440200000000000046bb1d0045020000000000001bc91d004602000000000000e3d61d004702000000000000c1e41d00480200000000000095f21d00490200000000000069001e004a020000000000003a0e1e004b02000000000000d3191e004c02000000000000251c1e004d02000000000000032a1e004e02000000000000cf371e004f02000000000000b53e1e0050020000000000009b451e00510200000000000078531e0052020000000000004c611e0053020000000000000b6f1e005402000000000000d37c1e005502000000000000488a1e00560200000000000012981e005702000000000000eda51e005802000000000000bdb31e0059020000000000008ac11e005a0200000000000067cf1e005b020000000000001fdd1e005c02000000000000ceea1e005d0200000000000088f81e005e0200000000000034061f005f02000000000000e0131f00600200000000000091211f006102000000000000532f1f006202000000000000fb3c1f006302000000000000bd4a1f00640200000000000066581f00650200000000000016661f006602000000000000bf731f00670200000000000070811f006802000000000000268f1f006902000000000000ca9c1f006a0200000000000071aa1f006b0200000000000023b81f006c02000000000000e7c51f006d020000000000007cd31f006e020000000000003ae11f006f02000000000000e1ee1f0070020000000000007dfc1f007102000000000000c5fe1f0072020000000000000e0120007302000000000000300a20007402000000000000e817200075020000000000007d2520007602000000000000053320007702000000000000944020007802000000000000294e20007902000000000000b45b20007a02000000000000386920007b02000000000000be7620007c020000000000004e8420007d02000000000000138b20007e02000000000000e59120007f020000000000007a9f200080020000000000002dad20008102000000000000d3ba2000820200000000000058c820008302000000000000f4d5200084020000000000008be32000850200000000000028f120008602000000000000c5fe200087020000000000005b0c21008802000000000000e41921008902000000000000271c21008a020000000000007c2721008b020000000000001c3521008c02000000000000b24221008d020000000000002c5021008e02000000000000c85d21008f020000000000006b6b21009002000000000000047921009102000000000000a98621009202000000000000469421009302000000000000d8a1210094020000000000007eaf210095020000000000001ebd21009602000000000000b0ca2100970200000000000060d821009802000000000000fae52100990200000000000091f321009a02000000000000210122009b02000000000000a20e22009c020000000000002e1c22009d02000000000000c02022009e02000000000000c82922009f0200000000000059372200a002000000000000d8442200a10200000000000074522200a2020000000000000c602200a302000000000000906d2200a402000000000000267b2200a502000000000000be882200a6020000000000004c962200a702000000000000d2a32200a8020000000000005eb12200a902000000000000e5be2200aa02000000000000a8c52200ab020000000000008dcc2200ac0200000000000067da2200ad02000000000000ebe52200ae0200000000000037e82200af0200000000000010f62200b002000000000000e3032300b1020000000000009f112300b202000000000000531f2300b3020000000000002c262300b4020000000000000f2d2300b50200000000000073382300b602000000000000bc3a2300b702000000000000053d2300b8020000000000009f482300b90200000000000093562300ba0200000000000082642300bb020000000000006f722300bc020000000000005f802300bd020000000000006d8e2300be02000000000000c5972300bf02000000000000739c2300c00200000000000081aa2300c10200000000000091b82300c2020000000000009fc62300c302000000000000afd42300c402000000000000bee22300c502000000000000ccf02300c602000000000000dafe2300c702000000000000e90c2400c802000000000000f51a2400c90200000000000005292400ca0200000000000013372400cb020000000000001b452400cc0200000000000026532400cd0200000000000034612400ce020000000000003e6f2400cf020000000000004e7d2400d0020000000000005d8b2400d1020000000000006d992400d2020000000000007ba72400d30200000000000088b52400d40200000000000096c32400d502000000000000a4d12400d602000000000000b3df2400d702000000000000c3ed2400d802000000000000bafb2400d902000000000000ad092500da02000000000000b0172500db02000000000000ae252500dc0200000000000052312500dd02000000000000aa332500de02000000000000ba412500df02000000000000c84f2500e002000000000000d55d2500e102000000000000e26b2500e202000000000000ec792500e302000000000000f4872500e40200000000000004962500e50200000000000014a42500e60200000000000021b22500e7020000000000002ec02500e8020000000000003dce2500e9020000000000004ddc2500ea0200000000000058ea2500eb0200000000000067f82500ec0200000000000077062600ed0200000000000085142600ee0200000000000094222600ef02000000000000a1302600f002000000000000ad3e2600f102000000000000b84c2600f202000000000000c55a2600f302000000000000d3682600f402000000000000c6762600f502000000000000ca842600f602000000000000d8922600f702000000000000e8a02600f802000000000000f4ae2600f90200000000000004bd2600fa0200000000000013cb2600fb0200000000000023d92600fc0200000000000033e72600fd0200000000000042f52600fe0200000000000052032700ff02000000000000621127000003000000000000711f27000103000000000000802d270002030000000000008f3b270003030000000000009549270004030000000000003e4e270005030000000000009e5727000603000000000000aa6527000703000000000000b97327000803000000000000c98127000903000000000000d68f27000a03000000000000e59d27000b03000000000000f2ab27000c0300000000000001ba27000d030000000000000dc827000e030000000000001ad627000f0300000000000027e42700100300000000000034f227001103000000000000340028001203000000000000420e28001303000000000000521c28001403000000000000602a280015030000000000006e38280016030000000000007d4628001703000000000000895428001803000000000000976228001903000000000000a47028001a03000000000000b47e28001b03000000000000b08c28001c03000000000000b59a28001d03000000000000c2a828001e03000000000000d1b628001f03000000000000e0c428002003000000000000eed228002103000000000000fde0280022030000000000000bef280023030000000000001bfd280024030000000000002b0b290025030000000000003b1929002603000000000000482729002703000000000000583529002803000000000000674329002903000000000000745129002a03000000000000845f29002b03000000000000946d29002c03000000000000a17b29002d03000000000000b18929002e03000000000000bd9729002f03000000000000cca529003003000000000000dbb329003103000000000000ebc129003203000000000000f6cf2900330300000000000004de2900340300000000000012ec2900350300000000000020fa2900360300000000000030082a0037030000000000003e162a0038030000000000004e242a0039030000000000005c322a003a0300000000000068402a003b03000000000000764e2a003c03000000000000845c2a003d03000000000000946a2a003e030000000000009e782a003f0300000000000055842a004003000000000000ad862a004103000000000000bb942a004203000000000000c4a22a004303000000000000d3b02a004403000000000000e3be2a004503000000000000eecc2a004603000000000000feda2a0047030000000000000ce92a0048030000000000006bf22a0049030000000000001bf72a004a0300000000000029052b004b030000000000007f072b004c0300000000000035132b004d0300000000000044212b004e03000000000000542f2b004f03000000000000633d2b005003000000000000714b2b00510300000000000081592b00520300000000000091672b0053030000000000009e752b0054030000000000009f832b005503000000000000a6912b005603000000000000ab9f2b005703000000000000b8ad2b005803000000000000c8bb2b005903000000000000d6c92b005a03000000000000e6d72b005b03000000000000f6e52b005c0300000000000005f42b005d0300000000000013022c005e0300000000000020102c005f03000000000000271e2c0060030000000000002d2c2c0061030000000000003d3a2c0062030000000000003b482c00630300000000000046562c00640300000000000055642c00650300000000000063722c00660300000000000070802c0067030000000000007f8e2c0068030000000000008e9c2c0069030000000000009baa2c006a030000000000009eb82c006b03000000000000aec62c006c03000000000000bcd42c006d03000000000000cae22c006e03000000000000d7f02c006f03000000000000e5fe2c007003000000000000f40c2d007103000000000000021b2d007203000000000000f8282d007303000000000000fe362d0074030000000000000c452d0075030000000000001b532d0076030000000000002a612d007703000000000000366f2d0078030000000000001a7d2d0079030000000000001f8b2d007a0300000000000018992d007b0300000000000024a72d007c030000000000002db52d007d03000000000000d6b92d007e0300000000000035c32d007f0300000000000042d12d00800300000000000052df2d00810300000000000061ed2d00820300000000000070fb2d0083030000000000007f092e0084030000000000008b172e0085030000000000009a252e008603000000000000a9332e008703000000000000b9412e008803000000000000c54f2e008903000000000000ce5d2e008a03000000000000db6b2e008b030000000000005a792e008c03000000000000f9862e008d03000000000000d6942e008e03000000000000e2a22e008f03000000000000dab02e009003000000000000ddbe2e009103000000000000e4cc2e009203000000000000f4da2e00930300000000000001e92e0094030000000000000cf72e0095030000000000001c052f0096030000000000002a132f00970300000000000036212f009803000000000000962a2f009903000000000000462f2f009a03000000000000533d2f009b03000000000000624b2f009c0300000000000070592f009d0300000000000078672f009e0300000000000084752f009f030000000000008e832f00a0030000000000009d912f00a103000000000000979f2f00a20300000000000099ad2f00a303000000000000a7bb2f00a403000000000000b4c92f00a503000000000000bfd72f00a603000000000000aae52f00a703000000000000baf32f00a803000000000000c9013000a903000000000000d80f3000aa03000000000000e51d3000ab03000000000000e72b3000ac03000000000000f6393000ad0300000000000005483000ae0300000000000015563000af0300000000000025643000b0030000000000002d6b3000b10300000000000034723000b20300000000000042803000b303000000000000508e3000b4030000000000005f9c3000b5030000000000006caa3000b6030000000000007ab83000b7030000000000007bc63000b8030000000000008bd43000b90300000000000099e23000ba03000000000000a8f03000bb03000000000000b5fe3000bc03000000000000b70c3100bd03000000000000a51a3100be030000000000009a283100bf0300000000000093363100c003000000000000a2443100c103000000000000b1523100c203000000000000bd603100c303000000000000cd6e3100c403000000000000dd7c3100c503000000000000ed8a3100c603000000000000fd983100c7030000000000000ca73100c8030000000000001bb53100c90300000000000029c33100ca0300000000000039d13100cb0300000000000047df3100cc0300000000000057ed3100cd0300000000000067fb3100ce0300000000000077093200cf0300000000000087173200d00300000000000096253200d103000000000000a6333200d203000000000000b5413200d303000000000000c54f3200d403000000000000d35d3200d503000000000000c96b3200d603000000000000bf793200d703000000000000b9873200d803000000000000b0953200d903000000000000aba33200da03000000000000bab13200db03000000000000c5bf3200dc03000000000000c5cd3200dd03000000000000d5db3200de03000000000000e5e93200df03000000000000f5f73200e00300000000000005063300e10300000000000014143300e20300000000000021223300e3030000000000002f303300e4030000000000003b3e3300e5030000000000004a4c3300e6030000000000005a5a3300e70300000000000068683300e80300000000000077763300e90300000000000087843300ea0300000000000096923300eb03000000000000a0a03300ec03000000000000afae3300ed03000000000000babc3300ee03000000000000caca3300ef03000000000000d9d83300f003000000000000e4e63300f103000000000000f1f43300f203000000000000f8023400f30300000000000007113400f403000000000000161f3400f503000000000000262d3400f603000000000000303b3400f7030000000000003f493400f8030000000000004c573400f90300000000000058653400fa0300000000000064733400fb030000000000006b813400fc03000000000000798f3400fd03000000000000889d3400fe0300000000000098ab3400ff03000000000000a7b934000004000000000000b7c734000104000000000000c5d534000204000000000000d5e334000304000000000000e3f134000404000000000000f3ff34000504000000000000030e35000604000000000000121c350007040000000000001d2a350008040000000000002531350009040000000000001f3835000a040000000000000c4635000b04000000000000f35335000c04000000000000016235000d040000000000000e7035000e040000000000001e7e35000f040000000000002e8c350010040000000000003c9a350011040000000000002da83500120400000000000022b63500130400000000000032c43500140400000000000041d23500150400000000000050e0350016040000000000005eee350017040000000000006efc350018040000000000002608360019040000000000007e0a36001a040000000000008b1836001b040000000000009b2636001c04000000000000aa3436001d04000000000000ba4236001e04000000000000ca5036001f04000000000000d95e360020040000000000009b6c36002104000000000000c97936002204000000000000488036002304000000000000198736002404000000000000a38b36002504000000000000f79436002604000000000000e4a236002704000000000000d6b036002804000000000000d0be36002904000000000000bfcc36002a04000000000000bdda36002b04000000000000c4e836002c04000000000000c7f636002d04000000000000d10437002e04000000000000da1237002f04000000000000df2037003004000000000000de2e37003104000000000000e03c37003204000000000000e84a37003304000000000000f15837003404000000000000495b37003504000000000000a15d370036040000000000000067370037040000000000000775370038040000000000000c8337003904000000000000fb9037003a040000000000006d9c37003b04000000000000bf9e37003c040000000000006da337003d04000000000000cbac37003e04000000000000d8ba37003f04000000000000e3c837004004000000000000edd637004104000000000000f7e43700420400000000000006f337004304000000000000150138004404000000000000200f380045040000000000002d1d38004604000000000000342b38004704000000000000423938004804000000000000074738004904000000000000ae5438004a04000000000000516238004b04000000000000ef6f38004c040000000000009a7d38004d04000000000000d77f38004e04000000000000878b38004f0400000000000091993800500400000000000098a738005104000000000000a1b538005204000000000000abc338005304000000000000b1d138005404000000000000b6df38005504000000000000bfed38005604000000000000c8fb38005704000000000000d60939005804000000000000db1039005904000000000000e01739005a04000000000000e82539005b04000000000000ef3339005c04000000000000e24139005d04000000000000e54f39005e04000000000000ef5d39005f04000000000000f96b39006004000000000000067a390061040000000000001288390062040000000000002096390063040000000000002da43900640400000000000038b23900650400000000000041c0390066040000000000004dce3900670400000000000059dc3900680400000000000066ea390069040000000000006bf839006a0400000000000077063a006b0400000000000080143a006c040000000000008a223a006d0400000000000093303a006e04000000000000ef393a006f040000000000009a3e3a007004000000000000a74c3a007104000000000000ae5a3a007204000000000000ba683a007304000000000000c7763a007404000000000000b7843a007504000000000000be923a0076040000000000009fa03a007704000000000000a3ae3a007804000000000000a6bc3a007904000000000000a6ca3a007a0400000000000090d83a007b0400000000000002e63a007c04000000000000d9f33a007d04000000000000db013b007e0400000000000033043b007f04000000000000eb0f3b0080040000000000009b143b008104000000000000f3163b008204000000000000fb1d3b0083040000000000000a2c3b0084040000000000001a3a3b0085040000000000002a483b00860400000000000039563b00870400000000000049643b0088040000000000004f723b0089040000000000005f803b008a0400000000000067873b008b040000000000006f8e3b008c04000000000000c7903b008d040000000000007f9c3b008e040000000000008faa3b008f040000000000008ab83b0090040000000000008dc63b009104000000000000e0c83b00920400000000000090cd3b00930400000000000098d43b009404000000000000a8e23b009504000000000000b6f03b009604000000000000befe3b009704000000000000c50c3c009804000000000000cd1a3c0099040000000000002d243c009a04000000000000dd283c009b04000000000000ed363c009c04000000000000fc443c009d040000000000000c533c009e040000000000001b613c009f040000000000002a6f3c00a0040000000000003a7d3c00a1040000000000004a8b3c00a20400000000000059993c00a30400000000000069a73c00a40400000000000079b53c00a50400000000000089c33c00a60400000000000099d13c00a704000000000000a8df3c00a804000000000000a5ed3c00a904000000000000b5fb3c00aa04000000000000c2093d00ab04000000000000ca173d00ac04000000000000da253d00ad04000000000000ea333d00ae04000000000000fa413d00af040000000000000a503d00b0040000000000001a5e3d00b1040000000000002a6c3d00b2040000000000003a7a3d00b3040000000000004a883d00b4040000000000005a963d00b5040000000000006aa43d00b6040000000000007ab23d00b7040000000000008ac03d00b8040000000000009ace3d00b904000000000000aadc3d00ba04000000000000baea3d00bb04000000000000caf83d00bc04000000000000da063e00bd04000000000000e9143e00be04000000000000f9223e00bf0400000000000009313e00c004000000000000193f3e00c104000000000000294d3e00c204000000000000395b3e00c30400000000000049693e00c40400000000000058773e00c50400000000000068853e00c60400000000000078933e00c70400000000000086a13e00c80400000000000096af3e00c904000000000000a6bd3e00ca04000000000000b6cb3e00cb04000000000000c6d93e00cc04000000000000d6e73e00cd04000000000000e5f53e00ce04000000000000f5033f00cf0400000000000005123f00d00400000000000015203f00d104000000000000252e3f00d204000000000000353c3f00d304000000000000454a3f00d40400000000000055583f00d5040000000000005d663f00d6040000000000006d743f00d7040000000000007d823f00d80400000000000085903f00d904000000000000959e3f00da04000000000000a5ac3f00db04000000000000b4ba3f00dc04000000000000c4c83f00dd04000000000000d4d63f00de04000000000000e4e43f00df0400000000000044ee3f00e004000000000000f4f23f00e10400000000000004014000e204000000000000140f4000e304000000000000241d4000e404000000000000342b4000e50400000000000044394000e60400000000000054474000e70400000000000064554000e80400000000000074634000e90400000000000084714000ea040000000000008d7f4000eb040000000000009b8d4000ec04000000000000aa9b4000ed04000000000000baa94000ee04000000000000cab74000ef04000000000000dac54000f004000000000000e9d34000f104000000000000f3e14000f20400000000000003f04000f30400000000000013fe4000f404000000000000230c4100f504000000000000331a4100f60400000000000043284100f70400000000000051364100f8040000000000005f444100f9040000000000006e524100fa040000000000007e604100fb040000000000008c6e4100fc040000000000009c7c4100fd04000000000000ac8a4100fe04000000000000bc984100ff04000000000000cca641000005000000000000dab441000105000000000000eac241000205000000000000f9d04100030500000000000009df4100040500000000000019ed4100050500000000000029fb410006050000000000003909420007050000000000003b17420008050000000000003a25420009050000000000003d3342000a05000000000000304142000b05000000000000304f42000c05000000000000295d42000d05000000000000246b42000e05000000000000297942000f050000000000002587420010050000000000002295420011050000000000001aa3420012050000000000001bb14200130500000000000019bf420014050000000000000ccd42001505000000000000f5da42001605000000000000e5e842001705000000000000c8f642001805000000000000c50443001905000000000000c31243001a05000000000000c62043001b05000000000000c42e43001c05000000000000bb3c43001d05000000000000b74a43001e05000000000000b25843001f05000000000000ac6643002005000000000000177443002105000000000000158143002205000000000000118e430023050000000000000c9b430024050000000000000da84300250500000000000013b54300260500000000000019c24300270500000000000031cf4300280500000000000022dc4300290500000000000046e943002a0500000000000051f643002b05000000000000620344002c050000000000006b1044002d050000000000007c1d44002e05000000000000962a44002f05000000000000a537440030050000000000007545440031050000000000006f53440032050000000000006e6144003305000000000000676f44003405000000000000697d44003505000000000000668b440036050000000000006099440037050000000000005fa74400380500000000000062b5440039050000000000005cc344003a0500000000000061d144003b0500000000000053df44003c0500000000000054ed44003d0500000000000055fb44003e05000000000000560945003f050000000000004e17450040050000000000004f2545004105000000000000a527450042050000000000004b33450043050000000000004241450044050000000000003e4f45004505000000000000315d450046050000000000002c6b450047050000000000002679450048050000000000001d8745004905000000000000129545004a0500000000000011a345004b0500000000000013b145004c050000000000000bbf45004d0500000000000008cd45004e050000000000000adb45004f0500000000000006e94500500500000000000003f745005105000000000000ff0446005205000000000000f81246005305000000000000ef20460054050000000000003f2a46005505000000000000e72e46005605000000000000eb3c46005705000000000000e54a460058050000000000003d5446005905000000000000ed5846005a05000000000000fd6646005b050000000000000d7546005c050000000000001d8346005d050000000000002d9146005e050000000000003d9f46005f050000000000004cad460060050000000000005cbb460061050000000000006bc94600620500000000000079d74600630500000000000089e54600640500000000000094f346006505000000000000a30147006605000000000000b30f47006705000000000000c21d47006805000000000000d22b47006905000000000000e23947006a05000000000000f24747006b05000000000000025647006c05000000000000126447006d05000000000000227247006e05000000000000318047006f05000000000000418e47007005000000000000519c4700710500000000000061aa4700720500000000000071b84700730500000000000081c64700740500000000000090d4470075050000000000009fe247007605000000000000adf047007705000000000000bdfe47007805000000000000cd0c48007905000000000000dd1a48007a05000000000000ed2848007b05000000000000fd3648007c050000000000000d4548007d050000000000001d5348007e050000000000002d6148007f050000000000003d6f480080050000000000004d7d480081050000000000005c8b480082050000000000006c99480083050000000000006ea74800840500000000000076b54800850500000000000086c34800860500000000000096d148008705000000000000a3df48008805000000000000b3ed48008905000000000000c3fb48008a05000000000000d30949008b05000000000000e31749008c05000000000000f32549008d05000000000000013449008e050000000000000e4249008f050000000000001e50490090050000000000002d5e490091050000000000003d6c490092050000000000004d7a490093050000000000005c88490094050000000000006b96490095050000000000007aa44900960500000000000089b24900970500000000000099c049009805000000000000a9ce49009905000000000000b8dc49009a050000000000009cea49009b0500000000000071f849009c0500000000000081064a009d0500000000000091144a009e05000000000000a1224a009f05000000000000b1304a00a005000000000000c03e4a00a105000000000000d04c4a00a205000000000000e05a4a00a305000000000000f0684a00a40500000000000000774a00a5050000000000000e854a00a6050000000000001e934a00a7050000000000002da14a00a8050000000000003baf4a00a9050000000000004abd4a00aa0500000000000059cb4a00ab0500000000000069d94a00ac0500000000000078e74a00ad0500000000000088f54a00ae0500000000000092034b00af05000000000000a1114b00b005000000000000b01f4b00b105000000000000c02d4b00b205000000000000d03b4b00b305000000000000df494b00b405000000000000ef574b00b505000000000000fd654b00b60500000000000055684b00b7050000000000000c744b00b805000000000000147b4b00b9050000000000006c7d4b00ba050000000000001c824b00bb0500000000000029904b00bc05000000000000369e4b00bd0500000000000045ac4b00be0500000000000050ba4b00bf05000000000000b0c34b00c00500000000000060c84b00c1050000000000006fd64b00c2050000000000007fe44b00c3050000000000008ff24b00c4050000000000009e004c00c505000000000000ad0e4c00c605000000000000bc1c4c00c705000000000000cc2a4c00c805000000000000db384c00c905000000000000eb464c00ca05000000000000f7544c00cb0500000000000007634c00cc0500000000000017714c00cd05000000000000247f4c00ce05000000000000348d4c00cf05000000000000449b4c00d00500000000000054a94c00d1050000000000005fb74c00d2050000000000006fc54c00d3050000000000007ed34c00d4050000000000008ee14c00d5050000000000009eef4c00d605000000000000acfd4c00d705000000000000b90b4d00d805000000000000c9194d00d905000000000000d9274d00da05000000000000e9354d00db05000000000000f9434d00dc0500000000000009524d00dd0500000000000019604d00de05000000000000296e4d00df05000000000000377c4d00e005000000000000158a4d00e10500000000000030974d00e20500000000000067a44d00e30500000000000074b24d00e40500000000000083c04d00e50500000000000093ce4d00e605000000000000a3dc4d00e705000000000000b3ea4d00e805000000000000c3f84d00e905000000000000d3064e00ea05000000000000e1144e00eb05000000000000f1224e00ec05000000000000a92e4e00ed0500000000000001314e00ee05000000000000113f4e00ef0500000000000019464e00f005000000000000174d4e00f105000000000000255b4e00f20500000000000035694e00f30500000000000045774e00f40500000000000053854e00f50500000000000062934e00f60500000000000070a14e00f70500000000000080af4e00f805000000000000d8b14e00f90500000000000090bd4e00fa05000000000000a0cb4e00fb05000000000000b0d94e00fc05000000000000bfe74e00fd05000000000000cdf54e00fe05000000000000dd034f00ff05000000000000ed114f000006000000000000fc1f4f00010600000000000003274f0002060000000000000a2e4f0003060000000000001a3c4f0004060000000000002a4a4f00050600000000000039584f00060600000000000049664f00070600000000000057744f00080600000000000067824f00090600000000000077904f000a06000000000000869e4f000b0600000000000096ac4f000c06000000000000a6ba4f000d06000000000000b3c84f000e06000000000000c3d64f000f06000000000000c8e44f001006000000000000d5f24f001106000000000000e50050001206000000000000f30e50001306000000000000021d500014060000000000000a2b50001506000000000000113950001606000000000000214750001706000000000000315550001806000000000000416350001906000000000000507150001a06000000000000607f50001b06000000000000708d50001c060000000000007f9b50001d060000000000008fa950001e0600000000000099b750001f06000000000000a8c550002006000000000000b8d350002106000000000000c8e15000220600000000000078e650002306000000000000cbef50002406000000000000dafd50002506000000000000e90b51002606000000000000f81951002706000000000000072851002806000000000000173651002906000000000000254451002a06000000000000345251002b06000000000000446051002c06000000000000536e51002d06000000000000637c51002e06000000000000728a51002f0600000000000070985100300600000000000065a6510031060000000000005bb45100320600000000000049c25100330600000000000032d05100340600000000000018de5100350600000000000009ec51003606000000000000f5f951003706000000000000e00752003806000000000000c41552003906000000000000a92352003a06000000000000913152003b06000000000000743f52003c060000000000005a4d52003d06000000000000585b52003e06000000000000506952003f0600000000000049775200400600000000000041855200410600000000000030935200420600000000000017a15200430600000000000007af52004406000000000000f4bc52004506000000000000deca52004606000000000000b7d85200470600000000000095e6520048060000000000007ff4520049060000000000006c0253004a06000000000000611053004b06000000000000421e53004c06000000000000282c53004d06000000000000063a53004e06000000000000f44753004f06000000000000dd5553005006000000000000ca6353005106000000000000cb7153005206000000000000b17f53005306000000000000978d53005406000000000000799b530055060000000000005fa95300560600000000000053b75300570600000000000039c5530058060000000000002ed35300590600000000000010e153005a06000000000000fcee53005b06000000000000f4f553005c06000000000000edfc53005d06000000000000dc0a54005e06000000000000b01854005f060000000000008026540060060000000000005b34540061060000000000004d4254006206000000000000325054006306000000000000225e540064060000000000000b6c54006506000000000000f37954006606000000000000df8754006706000000000000c79554006806000000000000ada35400690600000000000090b154006a060000000000007bbf54006b0600000000000064cd54006c0600000000000049db54006d0600000000000044e954006e060000000000002df754006f060000000000000b0555007006000000000000f51255007106000000000000d92055007206000000000000cc2e55007306000000000000c63c55007406000000000000c44a55007506000000000000c45855007606000000000000c76655007706000000000000c17455007806000000000000b88255007906000000000000b59055007a06000000000000b49e55007b06000000000000afac55007c06000000000000b3ba55007d06000000000000b6c855007e06000000000000b1d655007f06000000000000aae45500800600000000000092f2550081060000000000009200560082060000000000008f0e56008306000000000000891c56008406000000000000792a560085060000000000007738560086060000000000007446560087060000000000006b54560088060000000000006362560089060000000000005e7056008a06000000000000597e56008b06000000000000548c56008c06000000000000519a56008d060000000000004fa856008e060000000000003bb656008f0600000000000035c45600900600000000000030d2560091060000000000002be0560092060000000000002aee560093060000000000002bfc560094060000000000001e0a57009506000000000000181857009606000000000000142657009706000000000000083457009806000000000000084257009906000000000000095057009a060000000000000a5e57009b06000000000000076c57009c06000000000000fe7957009d06000000000000f78757009e06000000000000f49557009f06000000000000f5a35700a006000000000000f3b15700a106000000000000f5bf5700a206000000000000f3cd5700a306000000000000eddb5700a406000000000000e5e95700a506000000000000eaf75700a606000000000000ef055800a706000000000000ee135800a806000000000000e4215800a906000000000000e12f5800aa06000000000000e63d5800ab06000000000000e54b5800ac06000000000000e3595800ad06000000000000d1675800ae06000000000000c9755800af06000000000000cd835800b006000000000000bb915800b106000000000000b19f5800b206000000000000abad5800b306000000000000a2bb5800b40600000000000099c95800b5060000000000008cd75800b60600000000000080e55800b7060000000000007cf35800b80600000000000074015900b906000000000000740f5900ba06000000000000671d5900bb060000000000005d2b5900bc0600000000000054395900bd0600000000000049475900be060000000000003e555900bf0600000000000032635900c00600000000000025715900c106000000000000207f5900c206000000000000068d5900c306000000000000f79a5900c406000000000000efa85900c506000000000000ebb65900c606000000000000ebc45900c706000000000000ebd25900c806000000000000ede05900c906000000000000f2ee5900ca06000000000000fffc5900cb060000000000000d0b5a00cc060000000000001c195a00cd0600000000000027275a00ce0600000000000037355a00cf0600000000000046435a00d00600000000000055515a00d106000000000000635f5a00d206000000000000716d5a00d306000000000000817b5a00d40600000000000090895a00d5060000000000009f975a00d606000000000000ada55a00d706000000000000bdb35a00d806000000000000ccc15a00d906000000000000d7cf5a00da06000000000000e6dd5a00db06000000000000f5eb5a00dc0600000000000004fa5a00dd0600000000000012085b00de0600000000000022165b00df0600000000000032245b00e0060000000000003f325b00e1060000000000004e405b00e2060000000000005b4e5b00e306000000000000695c5b00e406000000000000bf5e5b00e50600000000000015615b00e606000000000000746a5b00e70600000000000084785b00e80600000000000092865b00e906000000000000a0945b00ea06000000000000ada25b00eb06000000000000b9b05b00ec06000000000000c2be5b00ed06000000000000d2cc5b00ee06000000000000e1da5b00ef06000000000000f0e85b00f006000000000000fef65b00f1060000000000000e055c00f20600000000000012135c00f3060000000000001a215c00f406000000000000172f5c00f5060000000000001f3d5c00f6060000000000002b4b5c00f70600000000000037595c00f80600000000000044675c00f90600000000000053755c00fa060000000000005f835c00fb060000000000006b915c00fc06000000000000769f5c00fd0600000000000080ad5c00fe0600000000000089bb5c00ff0600000000000040c75c00000700000000000097c95c000107000000000000a5d75c000207000000000000b2e55c000307000000000000bef35c000407000000000000cd015d000507000000000000dd0f5d000607000000000000df1d5d000707000000000000ee2b5d000807000000000000fa395d00090700000000000009485d000a0700000000000013565d000b0700000000000022645d000c0700000000000030725d000d070000000000003f805d000e070000000000004e8e5d000f070000000000005d9c5d0010070000000000006caa5d00110700000000000079b85d0012070000000000007dc65d0013070000000000008cd45d00140700000000000082e25d00150700000000000084f05d00160700000000000086fe5d001707000000000000920c5e0018070000000000009f1a5e001907000000000000a3285e001a07000000000000a7365e001b070000000000009f445e001c07000000000000ac525e001d07000000000000b9605e001e07000000000000c76e5e001f07000000000000647c5e002007000000000000fd895e002107000000000000bb905e00220700000000000004935e002307000000000000ad975e002407000000000000afa55e002507000000000000bdb35e002607000000000000cdc15e002707000000000000dbcf5e002807000000000000dcdd5e002907000000000000d9eb5e002a07000000000000daf95e002b07000000000000dc075f002c07000000000000df155f002d07000000000000d4235f002e07000000000000d4315f002f07000000000000d03f5f003007000000000000cc4d5f003107000000000000b85b5f003207000000000000c7695f003307000000000000d6775f003407000000000000e4855f003507000000000000f4935f00360700000000000004a25f00370700000000000014b05f00380700000000000024be5f00390700000000000034cc5f003a0700000000000043da5f003b0700000000000052e85f003c0700000000000062f65f003d07000000000000700460003e070000000000007f1260003f070000000000008f20600040070000000000009d2e60004107000000000000ad3c60004207000000000000bb4a60004307000000000000ca5860004407000000000000da6660004507000000000000e57460004607000000000000f48260004707000000000000039160004807000000000000129f6000490700000000000021ad60004a0700000000000030bb60004b0700000000000040c960004c070000000000004fd760004d070000000000005fe560004e070000000000006cf360004f070000000000007c0161005007000000000000880f61005107000000000000961d61005207000000000000a52b61005307000000000000b33961005407000000000000c34761005507000000000000d355610056070000000000009f63610057070000000000005371610058070000000000005c7f610059070000000000006c8d61005a07000000000000789b61005b0700000000000088a961005c0700000000000098b761005d07000000000000a7c561005e07000000000000b7d361005f07000000000000c6e16100600700000000000081ef6100610700000000000021fd61006207000000000000bc0a62006307000000000000601862006407000000000000082662006507000000000000943362006607000000000000374162006707000000000000d14e62006807000000000000805c620069070000000000002e6a62006a07000000000000dc7762006b070000000000007b8562006c070000000000000a9362006d07000000000000aaa062006e0700000000000047ae62006f07000000000000d5bb620070070000000000009ec962007107000000000000aed762007207000000000000bee562007307000000000000cdf362007407000000000000da0163007507000000000000e30f63007607000000000000f21d63007707000000000000022c63007807000000000000103a63007907000000000000204863007a07000000000000305663007b07000000000000406463007c07000000000000507263007d07000000000000608063007e07000000000000688e63007f07000000000000739c6300800700000000000083aa6300810700000000000092b863008207000000000000a0c66300830700000000000050cb63008407000000000000a8cd63008507000000000000b0d4630086070000000000000ede63008707000000000000bee263008807000000000000cef063008907000000000000c1fe63008a07000000000000d10c64008b07000000000000e01a64008c07000000000000f02864008d07000000000000ff3664008e070000000000000f4564008f070000000000001e53640090070000000000002d6164009107000000000000236f64009207000000000000297d64009307000000000000d28164009407000000000000308b64009507000000000000409964009607000000000000f09d640097070000000000004fa76400980700000000000057ae64009907000000000000afb064009a070000000000005eb564009b070000000000006ec364009c0700000000000079d164009d0700000000000088df64009e0700000000000094ed64009f07000000000000a3fb6400a007000000000000b0096500a107000000000000bb176500a207000000000000111a6500a307000000000000c6256500a407000000000000262f6500a507000000000000d6336500a607000000000000e4416500a707000000000000824d6500a807000000000000d34f6500a907000000000000cc5d6500aa070000000000001f606500ab07000000000000ca6b6500ac07000000000000c9796500ad070000000000001c836500ae07000000000000c7876500af07000000000000c6956500b007000000000000bca36500b10700000000000063af6500b207000000000000b5b16500b307000000000000b1bf6500b40700000000000056c46500b507000000000000a3cd6500b607000000000000a7db6500b707000000000000a4e96500b8070000000000004cf56500b907000000000000a3f76500ba070000000000009c056600bb070000000000009c136600bc0700000000000094216600bd07000000000000a12f6600be0700000000000000396600bf07000000000000b03d6600c007000000000000be4b6600c107000000000000cd596600c207000000000000dd676600c307000000000000ed756600c407000000000000fd836600c5070000000000000d926600c6070000000000001da06600c7070000000000002dae6600c8070000000000003dbc6600c9070000000000004dca6600ca070000000000005cd86600cb070000000000006ce66600cc0700000000000079f46600cd0700000000000088026700ce0700000000000097106700cf07000000000000a71e6700d007000000000000b62c6700d107000000000000c53a6700d207000000000000d4486700d307000000000000e3566700d407000000000000f3646700d50700000000000001736700d60700000000000011816700d707000000000000218f6700d807000000000000319d6700d9070000000000003dab6700da070000000000004cb96700db0700000000000058c76700dc0700000000000066d56700dd0700000000000075e36700de0700000000000082f16700df0700000000000090ff6700e0070000000000009d0d6800e107000000000000aa1b6800e207000000000000b4296800e307000000000000c2376800e407000000000000c5456800e507000000000000a8536800e60700000000000093616800e7070000000000007d6f6800e807000000000000607d6800e9070000000000005f8b6800ea070000000000005e996800eb0700000000000056a76800ec070000000000004fb56800ed0700000000000040c36800ee0700000000000033d16800ef0700000000000024df6800f0070000000000000bed6800f107000000000000f8fa6800f207000000000000d6086900f307000000000000b3166900f40700000000000090246900f5070000000000008c326900f6070000000000009b406900f707000000000000ab4e6900f807000000000000b45c6900f907000000000000c36a6900fa07000000000000d3786900fb07000000000000e1866900fc07000000000000f1946900fd0700000000000001a36900fe0700000000000011b16900ff0700000000000021bf690000080000000000002fcd6900010800000000000036d4690002080000000000003edb690003080000000000004ee9690004080000000000005ef7690005080000000000006e056a0006080000000000007e136a0007080000000000008e216a0008080000000000009e2f6a000908000000000000ad3d6a000a08000000000000bc4b6a000b08000000000000cc596a000c08000000000000dc676a000d08000000000000eb756a000e08000000000000fb836a000f080000000000000b926a0010080000000000001ba06a00110800000000000029ae6a00120800000000000038bc6a0013080000000000003fca6a0014080000000000004ed86a0015080000000000005ee66a0016080000000000006bf46a00170800000000000079026b0018080000000000007b106b0019080000000000008b1e6b001a08000000000000962c6b001b08000000000000a53a6b001c08000000000000b5486b001d08000000000000b8566b001e08000000000000ba646b001f08000000000000c5726b002008000000000000d2806b002108000000000000dc8e6b002208000000000000e3956b002308000000000000ea9c6b002408000000000000faaa6b0025080000000000000ab96b00260800000000000019c76b00270800000000000029d56b00280800000000000037e36b00290800000000000046f16b002a0800000000000056ff6b002b08000000000000640d6c002c080000000000006d1b6c002d0800000000000079296c002e0800000000000087376c002f0800000000000097456c003008000000000000a7536c003108000000000000b7616c003208000000000000c66f6c0033080000000000001e726c003408000000000000d67d6c0035080000000000008e896c003608000000000000e68b6c003708000000000000f5996c00380800000000000005a86c00390800000000000011b66c003a080000000000000ac46c003b08000000000000b5d16c003c080000000000008ddf6c003d0800000000000097ed6c003e08000000000000a7fb6c003f08000000000000b7096d004008000000000000c6176d004108000000000000d6256d004208000000000000e6336d004308000000000000f6416d00440800000000000006506d004508000000000000155e6d004608000000000000256c6d004708000000000000357a6d00480800000000000045886d00490800000000000055966d004a0800000000000065a46d004b0800000000000074b26d004c0800000000000084c06d004d0800000000000094ce6d004e08000000000000a4dc6d004f08000000000000b4ea6d005008000000000000c4f86d005108000000000000d4066e005208000000000000e4146e005308000000000000f4226e00540800000000000004316e005508000000000000143f6e005608000000000000244d6e005708000000000000345b6e00580800000000000043696e00590800000000000053776e005a08000000000000037c6e005b0800000000000063856e005c08000000000000138a6e005d0800000000000073936e005e0800000000000083a16e005f0800000000000091af6e00600800000000000098b66e006108000000000000a0bd6e006208000000000000b0cb6e006308000000000000bfd96e006408000000000000cfe76e006508000000000000dff56e006608000000000000ef036f006708000000000000ff116f0068080000000000000e206f0069080000000000001d2e6f006a080000000000002c3c6f006b080000000000003b4a6f006c0800000000000049586f006d0800000000000059666f006e0800000000000068746f006f0800000000000078826f00700800000000000088906f007108000000000000989e6f007208000000000000a6ac6f007308000000000000b5ba6f007408000000000000c3c86f007508000000000000d3d66f007608000000000000e3e46f007708000000000000f3f26f007808000000000000010170007908000000000000110f70007a08000000000000211d70007b080000000000002f2b70007c080000000000003f3970007d080000000000004f4770007e080000000000005f5570007f080000000000006f63700080080000000000007f71700081080000000000008f7f700082080000000000009e8d70008308000000000000ae9b70008408000000000000bea970008508000000000000ceb770008608000000000000dec570008708000000000000eed370008808000000000000fce1700089080000000000000cf070008a080000000000001cfe70008b080000000000002c0c71008c080000000000003c1a71008d080000000000004c2871008e08000000000000553671008f08000000000000654471009008000000000000755271009108000000000000856071009208000000000000956e71009308000000000000a57c71009408000000000000b48a71009508000000000000c49871009608000000000000d2a671009708000000000000e1b471009808000000000000c1c271009908000000000000d1d071009a08000000000000e1de71009b08000000000000f1ec71009c08000000000000fefa71009d080000000000000e0972009e080000000000001e1772009f080000000000002d257200a0080000000000003d337200a1080000000000004d417200a2080000000000005d4f7200a3080000000000006d5d7200a4080000000000006c6b7200a5080000000000007a797200a6080000000000008a877200a7080000000000009a957200a808000000000000aaa37200a908000000000000bab17200aa08000000000000cabf7200ab08000000000000d9cd7200ac08000000000000e9db7200ad08000000000000f9e97200ae0800000000000008f87200af0800000000000018067300b00800000000000028147300b10800000000000033227300b20800000000000043307300b308000000000000513e7300b408000000000000614c7300b508000000000000645a7300b60800000000000060687300b70800000000000059767300b80800000000000068847300b90800000000000078927300ba0800000000000088a07300bb0800000000000097ae7300bc080000000000008fbc7300bd0800000000000090ca7300be0800000000000086d87300bf0800000000000085e67300c00800000000000089f47300c10800000000000084027400c20800000000000088107400c3080000000000008d1e7400c4080000000000008e2c7400c5080000000000008a3a7400c6080000000000009a487400c708000000000000aa567400c808000000000000ba647400c908000000000000ca727400ca08000000000000da807400cb08000000000000ea8e7400cc08000000000000fa9c7400cd08000000000000feaa7400ce080000000000000eb97400cf080000000000001ec77400d0080000000000002ed57400d1080000000000003ee37400d2080000000000004ef17400d3080000000000005eff7400d4080000000000006a0d7500d508000000000000791b7500d60800000000000089297500d70800000000000098377500d80800000000000050437500d908000000000000a8457500da08000000000000b8537500db08000000000000c8617500dc08000000000000d86f7500dd08000000000000e87d7500de08000000000000f88b7500df08000000000000059a7500e00800000000000015a87500e10800000000000025b67500e20800000000000034c47500e30800000000000044d27500e40800000000000054e07500e50800000000000064ee7500e60800000000000074fc7500e708000000000000840a7600e80800000000000091187600e908000000000000a1267600ea08000000000000b1347600eb08000000000000c1427600ec08000000000000d1507600ed08000000000000cb5e7600ee0800000000000023617600ef08000000000000db6c7600f008000000000000eb7a7600f108000000000000fa887600f2080000000000000a977600f3080000000000001aa57600f4080000000000002ab37600f5080000000000003ac17600f60800000000000049cf7600f70800000000000059dd7600f80800000000000069eb7600f90800000000000079f97600fa0800000000000089077700fb0800000000000099157700fc08000000000000a9237700fd08000000000000b9317700fe08000000000000c73f7700ff08000000000000d64d77000009000000000000e65b77000109000000000000f6697700020900000000000005787700030900000000000015867700040900000000000024947700050900000000000034a27700060900000000000044b07700070900000000000054be7700080900000000000064cc7700090900000000000074da77000a0900000000000084e877000b0900000000000094f677000c09000000000000a40478000d09000000000000b41278000e09000000000000c42078000f09000000000000d42e78001009000000000000e43c78001109000000000000f44a78001209000000000000045978001309000000000000146778001409000000000000247578001509000000000000348378001609000000000000449178001709000000000000529f7800180900000000000062ad780019090000000000004abb78001a0900000000000058c978001b0900000000000068d778001c0900000000000074e578001d0900000000000083f378001e09000000000000eb0379001f09000000000000531479002009000000000000bb24790021090000000000002235790022090000000000008a4579002309000000000000f255790024090000000000005a6679002509000000000000c276790026090000000000002a8779002709000000000000919779002809000000000000f8a77900290900000000000050b879002a09000000000000b2c879002b0900000000000017d979002c0900000000000079e979002d09000000000000d9f979002e090000000000003c0a7a002f09000000000000a21a7a003009000000000000092b7a003109000000000000713b7a003209000000000000d84b7a0033090000000000003e5c7a003409000000000000a56c7a0035090000000000000d7d7a003609000000000000758d7a003709000000000000dd9d7a00380900000000000045ae7a003909000000000000adbe7a003a0900000000000014cf7a003b0900000000000060df7a003c09000000000000a3ef7a003d09000000000000e3ff7a003e09000000000000e60f7b003f09000000000000dd1f7b00400900000000000000297b004109000000000000f52f7b00420900000000000000407b004309000000000000d24f7b004409000000000000a35f7b004509000000000000656f7b004609000000000000607f7b0047090000000000006e8f7b0048090000000000005a9f7b00490900000000000016af7b004a0900000000000009b87b004b09000000000000c9be7b004c0900000000000095ce7b004d090000000000006ade7b004e0900000000000039ee7b004f090000000000001afe7b005009000000000000140e7c005109000000000000191e7c005209000000000000212e7c0053090000000000002a3e7c005409000000000000044e7c005509000000000000ff5d7c005609000000000000116e7c005709000000000000397e7c005809000000000000608e7c005909000000000000909e7c005a09000000000000cdae7c005b09000000000000e4be7c005c09000000000000fcce7c005d0900000000000013df7c005e0900000000000043ef7c005f0900000000000067ff7c006009000000000000890f7d0061090000000000000e1b7d006209000000000000af1f7d006309000000000000882d7d006409000000000000d62f7d00650900000000000005407d0066090000000000009c447d00670900000000000026507d00680900000000000055607d00690900000000000074707d006a0900000000000096807d006b09000000000000ba907d006c09000000000000dda07d006d0900000000000004b17d006e090000000000002bc17d006f0900000000000053d17d0070090000000000008ae17d007109000000000000bff17d007209000000000000f3017e00730900000000000023127e007409000000000000a81d7e0075090000000000004c227e0076090000000000008f327e007709000000000000cf427e00780900000000000011537e0079090000000000004e637e007a090000000000008f737e007b09000000000000cb837e007c09000000000000e8937e007d09000000000000aba17e007e09000000000000f7a37e007f090000000000002fb47e00800900000000000068c47e008109000000000000a3d47e008209000000000000e2e47e00830900000000000020f57e00840900000000000061057f008509000000000000050a7f008609000000000000a2157f008709000000000000e4257f0088090000000000001c367f0089090000000000005c467f008a0900000000000097567f008b09000000000000ce667f008c090000000000000e777f008d0900000000000047877f008e0900000000000083977f008f09000000000000c7a77f00900900000000000007b87f00910900000000000040c87f00920900000000000074d87f009309000000000000b2e87f009409000000000000eef87f0095090000000000002709800096090000000000006619800097090000000000005b2080009809000000000000bb2980009909000000000000233a80009a09000000000000554a80009b09000000000000aa5a80009c09000000000000025d80009d09000000000000016b80009e09000000000000697b80009f09000000000000d08b8000a009000000000000389c8000a109000000000000a0ac8000a20900000000000008bd8000a3090000000000006fcd8000a409000000000000d7dd8000a5090000000000003fee8000a609000000000000a7fe8000a709000000000000af058100a8090000000000000f0f8100a909000000000000771f8100aa09000000000000df2f8100ab0900000000000047408100ac09000000000000ae508100ad0900000000000016618100ae090000000000007e718100af090000000000002e768100b009000000000000c1818100b109000000000000a08f8100b209000000000000f3918100b30900000000000019a28100b40900000000000057b28100b509000000000000bfc28100b60900000000000027d38100b7090000000000008fe38100b809000000000000c0f38100b90900000000000003048200ba090000000000003c148200bb0900000000000079248200bc09000000000000af348200bd09000000000000ef448200be090000000000002b558200bf0900000000000062658200c00900000000000094758200c1090000000000006f858200c20900000000000096908200c30900000000000034958200c40900000000000063a58200c50900000000000057b38200c60900000000000090c38200c709000000000000cdd38200c8090000000000000ce48200c90900000000000051f48200ca0900000000000086048300cb09000000000000cf148300cc0900000000000018258300cd09000000000000b4298300ce09000000000000572e8300cf09000000000000076d8300d00900000000000054a28300d109000000000000d40a8400d20900000000000092208500d309000000000000f6308500d40900000000000059418500d509000000000000bf518500d609000000000000856b8500d709000000000000eb7b8500d809000000000000528c8500d909000000000000af9c8500da0900000000000013ad8500db090000000000007bbd8500dc09000000000000e3cd8500dd090000000000004bde8500de09000000000000b3ee8500df090000000000001bff8500e009000000000000820f8600e109000000000000ea1f8600e20900000000000052308600e309000000000000b3408600e409000000000000bb478600e5090000000000001b518600e6090000000000007f618600e709000000000000e7718600e8090000000000004c828600e909000000000000b4928600ea090000000000001ca38600eb0900000000000084b38600ec09000000000000dec38600ed090000000000002ed48600ee0900000000000086e48600ef09000000000000d7f48600f00900000000000028058700f1090000000000006d158700f2090000000000009d258700f309000000000000dc358700f40900000000000014468700f50900000000000056568700f609000000000000a8668700f709000000000000f4768700f8090000000000004f878700f90900000000000098978700fa09000000000000eba78700fb0900000000000043b88700fc0900000000000093c88700fd09000000000000f9d88700fe0900000000000061e98700ff09000000000000c9f98700000a000000000000300a8800010a000000000000981a8800020a000000000000ff2a8800030a000000000000673b8800040a000000000000cf4b8800050a000000000000375c8800060a0000000000009e6c8800070a000000000000067d8800080a0000000000006d8d8800090a000000000000cf9d88000a0a00000000000037ae88000b0a0000000000009fbe88000c0a00000000000007cf88000d0a0000000000006fdf88000e0a000000000000d1ef88000f0a00000000000039008900100a000000000000a1108900110a00000000000009218900120a00000000000071318900130a000000000000d9418900140a00000000000041528900150a000000000000a6628900160a000000000000b6708900170a0000000000000d738900180a00000000000075838900190a000000000000dd9389001a0a00000000000044a489001b0a000000000000a4ad89001c0a000000000000acb489001d0a00000000000013c589001e0a00000000000078d589001f0a000000000000dee58900200a00000000000044f68900210a000000000000ab068a00220a00000000000013178a00230a00000000000079278a00240a000000000000dc378a00250a0000000000003f488a00260a000000000000a6588a00270a00000000000008698a00280a00000000000007778a00290a0000000000005f798a002a0a000000000000c2898a002b0a000000000000299a8a002c0a00000000000091aa8a002d0a000000000000f9ba8a002e0a00000000000061cb8a002f0a000000000000c0d48a00300a00000000000070d98a00310a000000000000c8db8a00320a0000000000002eec8a00330a00000000000086ee8a00340a00000000000096fc8a00350a000000000000fe0c8b00360a000000000000661d8b00370a000000000000ce2d8b00380a000000000000de3b8b00390a000000000000363e8b003a0a0000000000009e4e8b003b0a000000000000065f8b003c0a0000000000006e6f8b003d0a000000000000d47f8b003e0a0000000000002c828b003f0a0000000000003c908b00400a0000000000009da08b00410a00000000000005b18b00420a0000000000006dc18b00430a000000000000d5d18b00440a0000000000003ce28b00450a000000000000a4f28b00460a0000000000000c038c00470a00000000000074138c00480a000000000000dc238c00490a00000000000044348c004a0a000000000000ac448c004b0a00000000000014558c004c0a0000000000007b658c004d0a000000000000e1758c004e0a00000000000049868c004f0a000000000000b1968c00500a00000000000018a78c00510a00000000000080b78c00520a000000000000e8c78c00530a00000000000050d88c00540a000000000000b8e88c00550a0000000000001ff98c00560a00000000000087098d00570a000000000000370e8d00580a000000000000e7128d00590a0000000000003f158d005a0a000000000000ef198d005b0a000000000000572a8d005c0a000000000000bf3a8d005d0a000000000000274b8d005e0a0000000000008f5b8d005f0a000000000000f76b8d00600a0000000000005f7c8d00610a000000000000c78c8d00620a0000000000002e9d8d00630a00000000000096ad8d00640a000000000000febd8d00650a00000000000066ce8d00660a000000000000cede8d00670a00000000000036ef8d00680a0000000000009eff8d00690a00000000000006108e006a0a0000000000006e208e006b0a000000000000d6308e006c0a0000000000003e418e006d0a000000000000a6518e006e0a0000000000000e628e006f0a00000000000076728e00700a000000000000de828e00710a00000000000046938e00720a000000000000aea38e00730a00000000000016b48e00740a0000000000007ec48e00750a000000000000e6d48e00760a0000000000004de58e00770a000000000000b3f58e00780a0000000000001b068f00790a00000000000083168f007a0a000000000000eb268f007b0a00000000000052378f007c0a000000000000ba478f007d0a00000000000022588f007e0a00000000000089688f007f0a000000000000ef788f00800a00000000000057898f00810a000000000000bf998f00820a00000000000077a58f00830a00000000000027aa8f00840a0000000000008fba8f00850a000000000000f7ca8f00860a0000000000005fdb8f00870a000000000000c7eb8f00880a0000000000002ffc8f00890a000000000000970c90008a0a000000000000ff1c90008b0a000000000000662d90008c0a000000000000ce3d90008d0a000000000000364e90008e0a000000000000935e90008f0a000000000000fb6e9000900a000000000000637f9000910a000000000000c98f9000920a0000000000002ca09000930a00000000000093b09000940a000000000000fac09000950a00000000000061d19000960a000000000000c8e19000970a0000000000002ff29000980a000000000000ed049100990a000000000000531591009a0a000000000000b12591009b0a000000000000173691009c0a000000000000764691009d0a000000000000db5691009e0a000000000000436791009f0a000000000000ab779100a00a0000000000000d889100a10a00000000000075989100a20a000000000000daa89100a30a00000000000042b99100a40a000000000000aac99100a50a00000000000012da9100a60a0000000000007aea9100a70a000000000000e1fa9100a80a000000000000490b9200a90a000000000000b11b9200aa0a000000000000192c9200ab0a000000000000813c9200ac0a000000000000e94c9200ad0a000000000000a95f9200ae0a00000000000011709200af0a00000000000079809200b00a000000000000d8909200b10a0000000000003fa19200b20a000000000000a7b19200b30a0000000000000fc29200b40a00000000000077d29200b50a000000000000dfe29200b60a00000000000047f39200b70a000000000000af039300b80a00000000000017149300b90a0000000000007f249300ba0a000000000000e6349300bb0a0000000000004d459300bc0a000000000000b5559300bd0a0000000000001d669300be0a00000000000085769300bf0a000000000000ed869300c00a00000000000055979300c10a000000000000baa79300c20a00000000000022b89300c30a0000000000008ac89300c40a000000000000f2d89300c50a0000000000005ae99300c60a000000000000c2f99300c70a0000000000002a0a9400c80a000000000000921a9400c90a000000000000f92a9400ca0a000000000000613b9400cb0a000000000000c74b9400cc0a0000000000002f5c9400cd0a000000000000976c9400ce0a000000000000fe7c9400cf0a000000000000668d9400d00a000000000000ce9d9400d10a00000000000032ae9400d20a0000000000009abe9400d30a000000000000ffce9400d40a00000000000067df9400d50a000000000000cfef9400d60a00000000000031009500d70a00000000000081109500d80a000000000000d1209500d90a000000000000f1309500da0a0000000000002f419500db0a00000000000068519500dc0a000000000000a2619500dd0a000000000000dc719500de0a00000000000003829500df0a0000000000003a929500e00a000000000000c4a49500e10a000000000000ffb49500e20a0000000000002ec59500e30a000000000000bed79500e40a00000000000009e89500e50a00000000000071f89500e60a000000000000d4089600e70a0000000000003c199600e80a000000000000a0299600e90a000000000000013a9600ea0a000000000000674a9600eb0a000000000000cd5a9600ec0a0000000000007d5f9600ed0a000000000000336b9600ee0a000000000000977b9600ef0a000000000000fc8b9600f00a0000000000005d9c9600f10a000000000000bfac9600f20a00000000000012bd9600f30a000000000000adcf9600f40a000000000000f7df9600f50a00000000000054f09600f60a00000000000001039700f70a0000000000005b139700f80a000000000000c1239700f90a00000000000068369700fa0a00000000000022499700fb0a000000000000d05b9700fc0a000000000000896e9700fd0a00000000000043819700fe0a000000000000a6919700ff0a000000000000b59f9700000b000000000000c3ad9700010b000000000000cfbb9700020b000000000000d7c99700030b000000000000dbd09700040b00000000000033d39700050b000000000000e3d79700060b0000000000003de19700070b000000000000ebe59700080b000000000000f2f39700090b000000000000fd0198000a0b000000000000031098000b0b000000000000051e98000c0b0000000000000c2c98000d0b000000000000103a98000e0b000000000000134898000f0b0000000000000d569800100b00000000000017649800110b00000000000014729800120b0000000000001a809800130b000000000000248e9800140b000000000000309c9800150b00000000000036aa9800160b00000000000041b89800170b0000000000004ac69800180b0000000000004fd49800190b00000000000058e298001a0b0000000000005ff098001b0b00000000000067fe98001c0b0000000000006b0c99001d0b0000000000006f1a99001e0b000000000000772899001f0b0000000000007a369900200b0000000000007c449900210b00000000000078529900220b00000000000074609900230b000000000000756e9900240b000000000000767c9900250b000000000000868a9900260b00000000000095989900270b000000000000a3a69900280b000000000000b1b49900290b000000000000bfc299002a0b000000000000ced099002b0b000000000000ddde99002c0b000000000000ecec99002d0b000000000000fcfa99002e0b0000000000000b099a002f0b0000000000001a179a00300b00000000000027259a00310b0000000000002d339a00320b0000000000002a419a00330b000000000000304f9a00340b0000000000002a5d9a00350b000000000000266b9a00360b0000000000001e799a00370b00000000000013879a00380b0000000000000c959a00390b00000000000004a39a003a0b000000000000feb09a003b0b000000000000f3be9a003c0b000000000000efcc9a003d0b000000000000e4da9a003e0b000000000000cbe89a003f0b000000000000b9f69a00400b000000000000ac049b00410b000000000000a1129b00420b00000000000091209b00430b0000000000007d2e9b00440b000000000000733c9b00450b0000000000006c4a9b00460b00000000000060589b00470b0000000000004b669b00480b00000000000036749b00490b00000000000023829b004a0b00000000000025909b004b0b0000000000000c9e9b004c0b00000000000007ac9b004d0b000000000000eeb99b004e0b000000000000d3c79b004f0b000000000000bad59b00500b0000000000009ce39b00510b0000000000007df19b00520b0000000000006cff9b00530b000000000000550d9c00540b000000000000481b9c00550b0000000000004b299c00560b00000000000022379c00570b0000000000000d459c00580b000000000000de529c00590b000000000000c1609c005a0b000000000000af6e9c005b0b0000000000009b7c9c005c0b0000000000007e8a9c005d0b00000000000062989c005e0b00000000000039a69c005f0b00000000000012b49c00600b000000000000f2c19c00610b000000000000dccf9c00620b000000000000b0dd9c00630b0000000000008ceb9c00640b00000000000075f99c00650b00000000000074079d00660b0000000000007d159d00670b0000000000008a239d00680b00000000000089319d00690b000000000000663f9d006a0b0000000000004b4d9d006b0b0000000000002f5b9d006c0b00000000000011699d006d0b000000000000fc769d006e0b000000000000e6849d006f0b000000000000c1929d00700b000000000000b7a09d00710b00000000000099ae9d00720b00000000000093bc9d00730b0000000000008dca9d00740b00000000000088d89d00750b00000000000072e69d00760b00000000000073f49d00770b00000000000073029e00780b0000000000006c109e00790b000000000000761e9e007a0b000000000000852c9e007b0b000000000000953a9e007c0b000000000000a5489e007d0b000000000000b4569e007e0b000000000000ba649e007f0b00000000000012679e00800b000000000000ca729e00810b000000000000d9809e00820b000000000000e98e9e00830b000000000000f89c9e00840b00000000000008ab9e00850b00000000000018b99e00860b00000000000028c79e00870b00000000000038d59e00880b000000000000e8d99e00890b00000000000048e39e008a0b00000000000058f19e008b0b00000000000068ff9e008c0b000000000000780d9f008d0b000000000000881b9f008e0b00000000000098299f008f0b000000000000a6379f00900b00000000000003439f00910b0000000000000b519f00920b0000000000001b5f9f00930b0000000000002b6d9f00940b0000000000003b7b9f00950b0000000000004b899f00960b0000000000005b979f00970b0000000000006aa59f00980b000000000000caae9f00990b0000000000007ab39f009a0b0000000000008ac19f009b0b0000000000009acf9f009c0b000000000000aadd9f009d0b000000000000baeb9f009e0b000000000000caf99f009f0b000000000000da07a000a00b000000000000ea15a000a10b000000000000fa23a000a20b0000000000000a32a000a30b0000000000001a40a000a40b000000000000294ea000a50b000000000000375ca000a60b000000000000476aa000a70b0000000000005778a000a80b0000000000006786a000a90b0000000000006294a000aa0b0000000000005ea2a000ab0b00000000000060b0a000ac0b00000000000063bea000ad0b00000000000073cca000ae0b00000000000083daa000af0b00000000000093e8a000b00b000000000000a3f6a000b10b000000000000b004a100b20b0000000000008f12a100b30b0000000000004a20a100b40b0000000000004b2ea100b50b0000000000005a3ca100b60b0000000000006a4aa100b70b0000000000006458a100b80b0000000000006d66a100b90b0000000000007d74a100ba0b0000000000008d82a100bb0b0000000000009d90a100bc0b0000000000009e9ea100bd0b000000000000aeaca100be0b000000000000bebaa100bf0b000000000000cec8a100c00b000000000000ddd6a100c10b000000000000ede4a100c20b000000000000fdf2a100c30b0000000000000d01a200c40b0000000000001c0fa200c50b0000000000007c18a200c60b000000000000241da200c70b000000000000d128a200c80b000000000000272ba200c90b0000000000001f32a200ca0b0000000000001e39a200cb0b0000000000001d47a200cc0b000000000000c84ba200cd0b0000000000001c55a200ce0b0000000000002063a200cf0b0000000000001f71a200d00b0000000000001c7fa200d10b000000000000208da200d20b0000000000001e9ba200d30b00000000000024a9a200d40b00000000000028b7a200d50b00000000000023c5a200d60b0000000000002fd3a200d70b0000000000003fe1a200d80b0000000000004fefa200d90b0000000000005ffda200da0b0000000000006f0ba300db0b0000000000007e19a300dc0b0000000000008e27a300dd0b0000000000009e35a300de0b000000000000ae43a300df0b000000000000bd51a300e00b000000000000cd5fa300e10b000000000000dd6da300e20b000000000000ed7ba300e30b000000000000fd89a300e40b0000000000000d98a300e50b0000000000001ca6a300e60b000000000000d4b1a300e70b0000000000002cb4a300e80b0000000000003cc2a300e90b00000000000043d0a300ea0b00000000000051dea300eb0b00000000000061eca300ec0b00000000000071faa300ed0b0000000000006f08a400ee0b0000000000007f16a400ef0b000000000000f4f1a600f00b0000000000000200a700f10b000000000000120ea700f20b0000000000001d1ca700f30b0000000000001f2aa700f40b0000000000002238a700f50b0000000000002146a700f60b0000000000002854a700f70b0000000000002162a700f80b0000000000002170a700f90b000000000000227ea700fa0b000000000000208ca700fb0b0000000000001a9aa700fc0b00000000000017a8a700fd0b00000000000019b6a700fe0b00000000000007c4a700ff0b00000000000009d2a700000c0000000000000fe0a700010c0000000000000beea700020c00000000000010fca700030c000000000000090aa800040c0000000000000618a800050c0000000000000226a800060c000000000000f333a800070c000000000000e241a800080c000000000000cd4fa800090c000000000000b15da8000a0c000000000000b964a8000b0c000000000000c06ba8000c0c000000000000d079a8000d0c000000000000807ea8000e0c000000000000d880a8000f0c000000000000df87a800100c000000000000e395a800110c000000000000f1a3a800120c00000000000001b2a800130c00000000000011c0a800140c00000000000021cea800150c00000000000031dca800160c00000000000041eaa800170c00000000000051f8a800180c0000000000006106a900190c0000000000007114a9001a0c0000000000007522a9001b0c0000000000008530a9001c0c000000000000953ea9001d0c000000000000a54ca9001e0c000000000000b55aa9001f0c000000000000c468a900200c000000000000c276a900210c000000000000bf84a900220c000000000000ce92a900230c000000000000dca0a900240c000000000000ecaea900250c000000000000efbca900260c00000000000092caa900270c0000000000002bd8a900280c000000000000bfe5a900290c0000000000005bf3a9002a0c0000000000000401aa002b0c000000000000ab0eaa002c0c0000000000006b1caa002d0c000000000000742aaa002e0c0000000000006638aa002f0c0000000000004d46aa00300c0000000000003b54aa00310c0000000000003f62aa00320c0000000000004270aa00330c000000000000407eaa00340c000000000000468caa00350c0000000000001f9aaa00360c00000000000025a8aa00370c00000000000035b6aa00380c00000000000045c4aa00390c00000000000055d2aa003a0c00000000000065e0aa003b0c00000000000075eeaa003c0c00000000000085fcaa003d0c000000000000950aab003e0c000000000000a518ab003f0c0000000000005c24ab00400c000000000000b426ab00410c000000000000c434ab00420c000000000000d242ab00430c000000000000e250ab00440c000000000000f25eab00450c000000000000016dab00460c000000000000117bab00470c0000000000002189ab00480c0000000000003197ab00490c00000000000041a5ab004a0c00000000000051b3ab004b0c00000000000061c1ab004c0c00000000000071cfab004d0c00000000000081ddab004e0c00000000000091ebab004f0c00000000000096f9ab00500c000000000000a307ac00510c000000000000b315ac00520c000000000000c323ac00530c000000000000d331ac00540c000000000000e33fac00550c000000000000f34dac00560c000000000000025cac00570c000000000000126aac00580c0000000000002078ac00590c0000000000003086ac005a0c0000000000003f94ac005b0c0000000000004fa2ac005c0c0000000000005cb0ac005d0c0000000000006cbeac005e0c0000000000007cccac005f0c0000000000008cdaac00600c0000000000009ce8ac00610c000000000000acf6ac00620c000000000000bc04ad00630c000000000000cc12ad00"}} \ No newline at end of file diff --git a/rust/crates/truapi-provider/tests/fixtures/paseo_people.json b/rust/crates/truapi-provider/tests/fixtures/paseo_people.json new file mode 100644 index 000000000..a416267cc --- /dev/null +++ b/rust/crates/truapi-provider/tests/fixtures/paseo_people.json @@ -0,0 +1,32 @@ +{ + "name": "Paseo People", + "id": "paseo-people", + "chainType": "Live", + "bootNodes": [ + "/dns/people-paseo-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWDsLbDyD8QLvvPW7L9s7tw8BKyFGh6UcPDm1LzWypY49u", + "/dns/people-paseo-boot-ng.dwellir.com/tcp/30371/p2p/12D3KooWDsLbDyD8QLvvPW7L9s7tw8BKyFGh6UcPDm1LzWypY49u", + "/dns/boot-node.helikon.io/tcp/10410/p2p/12D3KooWA3z8dVdADkDTN4xgpTkDo3AtsTq6o8nn7Xx54GjXXBgK", + "/dns/boot-node.helikon.io/tcp/10412/wss/p2p/12D3KooWA3z8dVdADkDTN4xgpTkDo3AtsTq6o8nn7Xx54GjXXBgK", + "/dns/people-paseo-bootnode.turboflakes.io/tcp/30840/p2p/12D3KooWPHNzviPuVsEsWNQQYL9gvfQ4BATKzYik3bh2p95pqvAJ", + "/dns/people-paseo-bootnode.turboflakes.io/tcp/30440/wss/p2p/12D3KooWPHNzviPuVsEsWNQQYL9gvfQ4BATKzYik3bh2p95pqvAJ", + "/dns/ibp-boot-people-paseo.luckyfriday.io/tcp/30339/p2p/12D3KooWL2M7HjQrvwEVw4EVr6y8LcggToyc9bgKN3LCsaq4UBq2", + "/dns/ibp-boot-people-paseo.luckyfriday.io/tcp/443/wss/p2p/12D3KooWL2M7HjQrvwEVw4EVr6y8LcggToyc9bgKN3LCsaq4UBq2", + "/dns/people-paseo.boot.stake.plus/tcp/30332/wss/p2p/12D3KooWPsVTRqGzYaJcR2JhZi3iBktegVBWvt9XaQbrVM3k6D9L", + "/dns/people-paseo.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWDVh1Srn7RHsu2wMN6tRjZ1DJACPMbkCcKo2ajvmENi3w", + "/dns/people-paseo.boot.rotko.net/tcp/30433/p2p/12D3KooWBbvBNSaUZsHoXfBYV1GdtAKRHDGb4eKiiG4nPJi5i2a7", + "/dns/people-paseo.boot.rotko.net/tcp/30435/wss/p2p/12D3KooWBbvBNSaUZsHoXfBYV1GdtAKRHDGb4eKiiG4nPJi5i2a7" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { + "ss58Format": 0, + "tokenDecimals": 10, + "tokenSymbol": "PAS" + }, + "relay_chain": "paseo", + "para_id": 1004, + "codeSubstitutes": {}, + "genesis": { + "stateRootHash": "0x2f6b718c1d9f0a87856807ce2540d4b90e55c5465b55cfdcdf721918a80a5779" + } +} diff --git a/rust/crates/truapi-provider/tests/live_paseo.rs b/rust/crates/truapi-provider/tests/live_paseo.rs new file mode 100644 index 000000000..073b1e487 --- /dev/null +++ b/rust/crates/truapi-provider/tests/live_paseo.rs @@ -0,0 +1,70 @@ +//! Live Paseo tests, excluded from CI. Run manually with: +//! +//! ```text +//! cargo test -p truapi-provider --features smoldot -- --ignored +//! ``` +//! +//! The light-client test reads the chain-spec path from `PASEO_CHAIN_SPEC`. + +#![cfg(all(feature = "ws", not(target_arch = "wasm32")))] + +use std::time::Duration; + +use futures::stream::StreamExt; +use serde_json::Value; +use truapi_platform::ChainProvider; +use truapi_provider::{ChainSource, EmbeddedChainProvider}; + +const PASEO_GENESIS: [u8; 32] = [0; 32]; // Registry key only; not validated. +const PASEO_WS_URL: &str = "wss://paseo.rpc.amforc.com"; + +async fn follow_initializes(source: ChainSource) { + let provider = EmbeddedChainProvider::builder() + .chain(PASEO_GENESIS, source) + .build(); + let connection = provider + .connect(PASEO_GENESIS) + .await + .expect("connecting to Paseo succeeds"); + let mut responses = connection.responses(); + connection.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainHead_v1_follow","params":[false]}"#.to_owned(), + ); + + let initialized = tokio::time::timeout(Duration::from_secs(300), async { + loop { + let frame = responses.next().await.expect("the connection stays alive"); + let frame: Value = serde_json::from_str(&frame).expect("frames are valid JSON"); + if frame["params"]["result"]["event"] == "initialized" { + return frame; + } + } + }) + .await + .expect("the follow reaches initialized in time"); + + assert!( + initialized["params"]["result"]["finalizedBlockHashes"] + .as_array() + .is_some_and(|hashes| !hashes.is_empty()), + "initialized must carry finalized hashes" + ); + connection.close(); +} + +#[tokio::test] +#[ignore = "requires network access to Paseo"] +async fn ws_follow_initializes() { + let url = url::Url::parse(PASEO_WS_URL).expect("static URL parses"); + follow_initializes(ChainSource::rpc_node(url)).await; +} + +#[cfg(feature = "smoldot")] +#[tokio::test] +#[ignore = "requires network access to Paseo and PASEO_CHAIN_SPEC"] +async fn light_follow_initializes() { + let path = std::env::var("PASEO_CHAIN_SPEC") + .expect("set PASEO_CHAIN_SPEC to a Paseo relay chain-spec path"); + let spec = std::fs::read_to_string(path).expect("the chain spec is readable"); + follow_initializes(ChainSource::light_client(spec).build()).await; +} diff --git a/rust/crates/truapi-provider/tests/web_browser.rs b/rust/crates/truapi-provider/tests/web_browser.rs new file mode 100644 index 000000000..a09882107 --- /dev/null +++ b/rust/crates/truapi-provider/tests/web_browser.rs @@ -0,0 +1,160 @@ +//! Browser tests for the wasm32 WebSocket backend and the JS-facing API. +//! +//! Run headless (a local gateway provides the endpoint; the URL is baked in +//! at compile time via `TRUAPI_PROVIDER_TEST_WS`): +//! +//! ```text +//! cargo run -p truapi-provider --features networks --example gateway -- \ +//! rust/crates/truapi-provider/examples/gateway-dotli-paseo-next-v2.json & +//! TRUAPI_PROVIDER_TEST_WS=ws://127.0.0.1:9944/relay \ +//! wasm-pack test --headless --chrome rust/crates/truapi-provider --features js +//! ``` +//! +//! Without the env var only the offline failure paths run; the gateway +//! round-trips log that they were skipped rather than passing silently. + +#![cfg(all(target_arch = "wasm32", feature = "ws"))] + +use futures::stream::StreamExt; +use truapi_platform::ChainProvider; +use truapi_provider::{ChainSource, EmbeddedChainProvider}; +use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; + +wasm_bindgen_test_configure!(run_in_browser); + +/// Gateway route to test against, baked in at compile time. +const TEST_WS: Option<&str> = option_env!("TRUAPI_PROVIDER_TEST_WS"); + +/// The gateway route, or `None` after logging that `test` is being skipped. +/// +/// A vacuous pass is indistinguishable from a real one in the test output, so +/// the skip is announced in the browser console the runner captures. +fn gateway_url(test: &str) -> Option<&'static str> { + if TEST_WS.is_none() { + web_sys::console::warn_1( + &format!("SKIP {test}: set TRUAPI_PROVIDER_TEST_WS to run it against a gateway").into(), + ); + } + TEST_WS +} + +const GENESIS: [u8; 32] = [7; 32]; + +fn provider(url: &str) -> EmbeddedChainProvider { + let url = url::Url::parse(url).expect("test URL parses"); + EmbeddedChainProvider::builder() + .chain(GENESIS, ChainSource::rpc_node(url)) + .build() +} + +#[wasm_bindgen_test] +async fn unknown_genesis_is_an_error() { + let error = provider("ws://127.0.0.1:1") + .connect([9; 32]) + .await + .err() + .expect("connect must fail for an unregistered genesis"); + assert!(error.reason.contains(&"09".repeat(32))); +} + +#[wasm_bindgen_test] +async fn handshake_failure_is_an_error() { + let error = provider("ws://127.0.0.1:1") + .connect(GENESIS) + .await + .err() + .expect("connecting to a closed port must fail"); + assert!( + error.reason.contains("WebSocket"), + "unexpected: {}", + error.reason + ); +} + +#[wasm_bindgen_test] +async fn round_trip_and_close_against_gateway() { + let Some(url) = gateway_url("round_trip_and_close_against_gateway") else { + return; // Offline run: covered by the failure-path tests above. + }; + let connection = provider(url) + .connect(GENESIS) + .await + .expect("connecting to the gateway succeeds"); + let mut responses = connection.responses(); + + connection.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_genesisHash","params":[]}"#.to_owned(), + ); + let response = responses.next().await.expect("the response arrives"); + assert!(response.contains("\"id\":1"), "unexpected: {response}"); + assert!(response.contains("0x"), "unexpected: {response}"); + + connection.close(); + connection.close(); + assert_eq!(responses.next().await, None); + // A late send must not panic on the closed connection. + connection.send(r#"{"jsonrpc":"2.0","id":2,"method":"x","params":[]}"#.to_owned()); +} + +/// The embedded light client answers spec-local queries in the browser +/// without any network: proves the wasm smoldot platform end-to-end. +#[cfg(feature = "smoldot")] +#[wasm_bindgen_test] +async fn light_client_chain_name_round_trips_in_browser() { + let provider = EmbeddedChainProvider::builder() + .chain( + GENESIS, + ChainSource::light_client(include_str!("fixtures/paseo.json")).build(), + ) + .build(); + let connection = provider + .connect(GENESIS) + .await + .expect("add_chain succeeds in the browser"); + let mut responses = connection.responses(); + connection.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_chainName","params":[]}"#.to_owned(), + ); + let response = responses.next().await.expect("smoldot answers locally"); + assert!( + response.contains("Paseo Testnet"), + "unexpected response: {response}" + ); + connection.close(); + assert_eq!(responses.next().await, None); +} + +#[wasm_bindgen_test] +async fn js_api_round_trip_against_gateway() { + use truapi_provider::js::{ChainProviderBuilder, Connection}; + + let Some(url) = gateway_url("js_api_round_trip_against_gateway") else { + return; + }; + let mut builder = ChainProviderBuilder::new(); + builder + .add_rpc_chain(&format!("0x{}", "07".repeat(32)), url) + .expect("chain registers"); + let handle = builder.build().expect("provider builds"); + + let connection: Connection = handle + .connect(&format!("0x{}", "07".repeat(32))) + .await + .expect("connect resolves"); + + connection.send( + r#"{"jsonrpc":"2.0","id":1,"method":"chainSpec_v1_genesisHash","params":[]}"#.to_owned(), + ); + let response = connection + .next_response() + .await + .expect("the response arrives"); + assert!(response.contains("\"id\":1"), "unexpected: {response}"); + + connection.close(); + assert_eq!( + connection.next_response().await, + None, + "the stream must end after close" + ); +} diff --git a/rust/crates/truapi-provider/tests/ws_loopback.rs b/rust/crates/truapi-provider/tests/ws_loopback.rs new file mode 100644 index 000000000..fbd480f9c --- /dev/null +++ b/rust/crates/truapi-provider/tests/ws_loopback.rs @@ -0,0 +1,139 @@ +//! WebSocket backend integration tests against an in-process jsonrpsee server. + +#![cfg(all(feature = "ws", not(target_arch = "wasm32")))] + +use std::net::SocketAddr; +use std::time::Duration; + +use futures::stream::StreamExt; +use jsonrpsee::core::SubscriptionResult; +use jsonrpsee::server::{RpcModule, Server, ServerHandle, SubscriptionMessage}; +use serde_json::Value; +use truapi_platform::ChainProvider; +use truapi_provider::{ChainSource, EmbeddedChainProvider}; + +const GENESIS: [u8; 32] = [7; 32]; + +async fn spawn_server() -> (SocketAddr, ServerHandle) { + let server = Server::builder() + .build("127.0.0.1:0") + .await + .expect("loopback server binds"); + let addr = server.local_addr().expect("server has a local address"); + + let mut module = RpcModule::new(()); + module + .register_method("echo", |params, (), _extensions| { + params.one::().unwrap_or_default() + }) + .expect("echo registers"); + module + .register_subscription( + "sub_ticks", + "tick", + "unsub_ticks", + |_params, pending, _context, _extensions| async move { + let sink = pending.accept().await?; + for tick in 0..3u32 { + sink.send(SubscriptionMessage::from_json(&tick)?).await?; + } + SubscriptionResult::Ok(()) + }, + ) + .expect("sub_ticks registers"); + + (addr, server.start(module)) +} + +async fn connect(addr: SocketAddr) -> Box { + let url = url::Url::parse(&format!("ws://{addr}")).expect("loopback URL parses"); + EmbeddedChainProvider::builder() + .chain(GENESIS, ChainSource::rpc_node(url)) + .build() + .connect(GENESIS) + .await + .expect("loopback connect succeeds") +} + +#[tokio::test] +async fn request_response_round_trip() { + let (addr, _server) = spawn_server().await; + let connection = connect(addr).await; + let mut responses = connection.responses(); + + connection.send(r#"{"jsonrpc":"2.0","id":1,"method":"echo","params":["ping"]}"#.to_owned()); + let frame: Value = + serde_json::from_str(&responses.next().await.expect("the echo response arrives")) + .expect("frames are valid JSON"); + assert_eq!(frame["id"], 1); + assert_eq!(frame["result"], "ping"); +} + +#[tokio::test] +async fn subscription_notifications_pass_through_raw() { + let (addr, _server) = spawn_server().await; + let connection = connect(addr).await; + let mut responses = connection.responses(); + + connection.send(r#"{"jsonrpc":"2.0","id":2,"method":"sub_ticks","params":[]}"#.to_owned()); + let ack: Value = serde_json::from_str( + &responses + .next() + .await + .expect("the subscription ack arrives"), + ) + .expect("frames are valid JSON"); + assert_eq!(ack["id"], 2); + let subscription = ack["result"].clone(); + + for expected in 0..3u32 { + let frame: Value = + serde_json::from_str(&responses.next().await.expect("tick notifications arrive")) + .expect("frames are valid JSON"); + assert_eq!(frame["method"], "tick"); + assert_eq!(frame["params"]["subscription"], subscription); + assert_eq!(frame["params"]["result"], expected); + } +} + +#[tokio::test] +async fn server_shutdown_ends_the_stream() { + let (addr, server) = spawn_server().await; + let connection = connect(addr).await; + let mut responses = connection.responses(); + + server.stop().expect("server stops"); + server.stopped().await; + + let end = tokio::time::timeout(Duration::from_secs(10), responses.next()).await; + assert_eq!(end.expect("the stream ends after server shutdown"), None); +} + +#[tokio::test] +async fn close_ends_the_stream() { + let (addr, _server) = spawn_server().await; + let connection = connect(addr).await; + let mut responses = connection.responses(); + + connection.close(); + connection.close(); + assert_eq!(responses.next().await, None); + // A late send must not panic on the closed connection. + connection.send(r#"{"jsonrpc":"2.0","id":3,"method":"echo","params":["late"]}"#.to_owned()); +} + +#[tokio::test] +async fn connections_are_independent() { + let (addr, _server) = spawn_server().await; + let first = connect(addr).await; + let second = connect(addr).await; + let mut second_responses = second.responses(); + + first.send(r#"{"jsonrpc":"2.0","id":4,"method":"echo","params":["mine"]}"#.to_owned()); + let cross_talk = + tokio::time::timeout(Duration::from_millis(500), second_responses.next()).await; + assert!( + cross_talk.is_err(), + "the second connection must not observe the first connection's response" + ); +} diff --git a/scripts/update-chain-specs.sh b/scripts/update-chain-specs.sh new file mode 100755 index 000000000..db04266e6 --- /dev/null +++ b/scripts/update-chain-specs.sh @@ -0,0 +1,427 @@ +#!/usr/bin/env bash +# Refresh the bundled smoldot chain specs in rust/crates/truapi-provider/networks +# from their live chains. +# +# Each spec's genesis.stateRootHash is set from the chain's block 0, so the spec keeps matching the +# chain's genesis after a wipe; a stale genesis stops smoldot from syncing. Relay specs additionally +# get a fresh lightSyncState checkpoint, which reduces smoldot warp-sync time from ~12s to ~1-3s. +# +# The genesis and the checkpoint are what a light client trusts in place of syncing from block 0, so +# neither is taken on a single endpoint's word. Every reachable endpoint must serve the same genesis +# state root, and an endpoint other than the one that served the checkpoint must agree on the block +# it pins. A network with only one endpoint cannot be corroborated and says so. +# +# These specs are compiled into the @parity/truapi-provider wasm via include_str!, so a refresh only +# reaches consumers (e.g. dotli) after a new provider version is published and they bump to it. +# +# Usage: bash scripts/update-chain-specs.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +SPECS_DIR="$PROJECT_DIR/rust/crates/truapi-provider/networks" + +# Each refresh_spec call is `|| true` so one network's outage cannot block the +# others, and that disables `set -e` for the whole function body. Every failure +# inside it therefore has to be checked by hand, starting with the tools it needs: +# without this, a missing `node` reports every spec as refreshed while writing +# nothing. +for tool in node jq curl; do + command -v "$tool" >/dev/null || { + echo "error: $tool is required but not on PATH" >&2 + exit 1 + } +done + +# Timeout (seconds) for all curl calls. +TIMEOUT=30 + +# Set when a spec could not be trusted: endpoints disagreed about its genesis or checkpoint, or no +# second endpoint could corroborate the checkpoint at all. Either way the spec is left unchanged and +# the run fails, so a trust anchor is never taken from one endpoint on its own. +INTEGRITY_FAILURES=0 + +# Health-check the candidate bootNodes (env var BOOTNODES) and keep only the reachable ones. +# Set env var SKIP_BOOTNODE_CHECK=true to leave them unchanged. +BOOTNODES_JS=' +const fs = require("fs"); +const net = require("net"); + +const skipBootnodeCheck = process.env.SKIP_BOOTNODE_CHECK === "true"; + +function parseMultiaddr(ma) { + const parts = ma.split("/").filter(Boolean); + let host = null, port = null; + for (let i = 0; i < parts.length; i++) { + if (["dns", "dns4", "dns6"].includes(parts[i]) && parts[i + 1]) { + host = parts[i + 1]; + } else if (parts[i] === "ip4" && parts[i + 1]) { + host = parts[i + 1]; + } else if (parts[i] === "tcp" && parts[i + 1]) { + port = parseInt(parts[i + 1], 10); + } + } + return { host, port }; +} + +function testBootnode(ma, timeoutMs = 5000) { + return new Promise((resolve) => { + const { host, port } = parseMultiaddr(ma); + if (!host || !port) { + resolve({ ma, healthy: false, reason: "unparseable" }); + return; + } + const socket = net.createConnection({ host, port, timeout: timeoutMs }); + socket.on("connect", () => { + socket.destroy(); + resolve({ ma, healthy: true }); + }); + socket.on("timeout", () => { + socket.destroy(); + resolve({ ma, healthy: false, reason: "timeout" }); + }); + socket.on("error", (err) => { + socket.destroy(); + resolve({ ma, healthy: false, reason: err.code || err.message }); + }); + }); +} + +(async () => { + const specPath = process.argv[1]; + const spec = JSON.parse(fs.readFileSync(specPath, "utf8")); + + if (skipBootnodeCheck) { + console.log(" Bootnode health check SKIPPED, keeping existing bootnodes."); + console.log(" Bootnodes (unchanged): " + spec.bootNodes.length); + return; + } + + const candidates = JSON.parse(process.env.BOOTNODES); + console.log(" Testing " + candidates.length + " bootnodes (5s timeout each)..."); + const results = await Promise.all(candidates.map((bn) => testBootnode(bn))); + const healthy = []; + for (const r of results) { + const short = r.ma.length > 80 ? r.ma.substring(0, 77) + "..." : r.ma; + if (r.healthy) { + console.log(" ok " + short); + healthy.push(r.ma); + } else { + console.log(" x " + short + " (" + r.reason + ")"); + } + } + console.log(" Healthy: " + healthy.length + "/" + candidates.length); + if (healthy.length === 0) { + console.log(" WARNING: No healthy bootnodes found, keeping original."); + } else { + spec.bootNodes = healthy; + fs.writeFileSync(specPath, JSON.stringify(spec)); + } + console.log(" Bootnodes: " + spec.bootNodes.length); +})(); +' + +# Corroborate a relay's warp-sync checkpoint against endpoints other than the one that served it. +# +# The checkpoint is what a light client trusts in place of syncing from genesis, so it is never taken +# on one endpoint's word. Each peer in PEER_RPCS is asked to confirm two things about the block +# CHECKPOINT_HEADER pins (its first 32 bytes are the parent hash, the compact integer after them the +# block number): +# +# * The block itself. The peer must report the same parent hash for the canonical block at that +# height. Finalized blocks do not reorg, so a peer that answers and disagrees is serving a +# different chain. +# * The GRANDPA authorities at that block, which is what finality is verified against. A genuine +# header paired with a forged authority set would otherwise pass. The authorities are read from +# the peer with a runtime call at the pinned block hash, so the two sides describe the same +# block and compare byte for byte, with no set-id bookkeeping to reason about. That call also +# keeps the response small; asking a peer for its whole chain spec would return the raw genesis +# storage with it. +# +# Corroboration requires a peer that confirms the block. A peer that is unreachable, that has not +# reached the pinned height, or that cannot serve state for it is skipped, and if no peer is left +# the refresh fails rather than accepting the primary alone. Only a network configured with a single +# endpoint is allowed through uncorroborated, because there is nothing to ask. +CHECKPOINT_JS=' +const header = process.env.CHECKPOINT_HEADER.replace(/^0x/, ""); +const authoritySet = (process.env.CHECKPOINT_AUTHORITY_SET || "").replace(/^0x/, ""); +const peers = JSON.parse(process.env.PEER_RPCS); +const parentHash = "0x" + header.slice(0, 64); + +// SCALE compact integer at a byte offset, with the width it occupies. +function compactAt(hex, offset) { + const byte = parseInt(hex.slice(offset * 2, offset * 2 + 2), 16); + const mode = byte & 0b11; + const width = mode === 0 ? 1 : mode === 1 ? 2 : mode === 2 ? 4 : 0; + if (width === 0) throw new Error("big-integer compact values are not supported"); + const le = hex.slice(offset * 2, offset * 2 + width * 2); + const bytes = le.match(/../g).map((b) => parseInt(b, 16)); + let value = 0; + for (let i = bytes.length - 1; i >= 0; i--) value = value * 256 + bytes[i]; + return { value: value >>> 2, width }; +} + +// A GRANDPA AuthoritySet is current_authorities (32-byte public key plus 8-byte weight each) +// followed by the set id and pending changes. Its leading authority vector is encoded exactly as +// GrandpaApi_grandpa_authorities returns it, so that prefix is what a peer can be asked to confirm. +function checkpointAuthorities() { + if (authoritySet.length === 0) return null; + const count = compactAt(authoritySet, 0); + const end = count.width + count.value * 40; + if (authoritySet.length < end * 2) return null; + return { hex: authoritySet.slice(0, end * 2), count: count.value }; +} + +async function rpc(url, method, params) { + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id: 1, jsonrpc: "2.0", method, params }), + signal: AbortSignal.timeout(Number(process.env.TIMEOUT || 30) * 1000), + }); + const body = await response.json(); + if (body.error) throw new Error(body.error.message || "RPC error"); + return body.result; +} + +// Compare the authorities the peer reports at the pinned block. A peer that cannot serve them +// (pruned state, unsupported call, a blip) downgrades this peer to block-only corroboration rather +// than discarding a parent hash that already matched. +async function authoritiesDisagree(peer, blockHash, ours) { + let theirs; + try { + const raw = await rpc(peer, "state_call", ["GrandpaApi_grandpa_authorities", "0x", blockHash]); + theirs = raw ? raw.replace(/^0x/, "") : ""; + } catch (error) { + console.log(" ? " + peer + " served no authorities (" + (error.message || error) + ")"); + return false; + } + if (theirs.length === 0) { + console.log(" ? " + peer + " served no authorities, confirming the block only"); + return false; + } + if (theirs !== ours.hex) { + console.log(" x " + peer + " reports different GRANDPA authorities at the pinned block"); + console.log(" checkpoint carries " + ours.count + " authorities"); + console.log(" peer reports " + compactAt(theirs, 0).value); + return true; + } + console.log(" ok " + ours.count + " GRANDPA authorities agree with " + peer); + return false; +} + +(async () => { + const number = compactAt(header, 32).value; + const ours = checkpointAuthorities(); + if (ours === null) { + console.log(" ? checkpoint carries no readable authority set, confirming the block only"); + } + + for (const peer of peers) { + let blockHash; + let peerParent; + try { + blockHash = await rpc(peer, "chain_getBlockHash", [number]); + if (!blockHash) { + console.log(" ? " + peer + " has not reached block " + number); + continue; + } + peerParent = (await rpc(peer, "chain_getHeader", [blockHash])).parentHash; + } catch (error) { + console.log(" ? " + peer + " did not answer (" + (error.message || error) + ")"); + continue; + } + if (peerParent.toLowerCase() !== parentHash.toLowerCase()) { + console.log(" x " + peer + " disagrees at block " + number); + console.log(" checkpoint parent " + parentHash); + console.log(" peer parent " + peerParent); + process.exit(1); + } + if (ours !== null && (await authoritiesDisagree(peer, blockHash, ours))) { + process.exit(1); + } + console.log(" ok corroborated at block " + number + " by " + peer); + return; + } + + if (peers.length > 0) { + console.log( + " ERROR: none of the " + + peers.length + + " other endpoint(s) confirmed block " + + number + + ". They were unreachable, behind it, or both; either way the checkpoint rests on one" + + " endpoint alone, which is also how a checkpoint pinned ahead of every peer would look.", + ); + process.exit(1); + } + console.log(" WARNING: only one endpoint is configured, so the checkpoint cannot be corroborated."); +})(); +' + +# Fetch the genesis state root. +fetch_state_root() { + local rpc="$1" + local block0 + block0=$(curl -s --max-time "$TIMEOUT" -H "Content-Type: application/json" \ + -d '{"id":1,"jsonrpc":"2.0","method":"chain_getBlockHash","params":[0]}' "$rpc" 2>/dev/null \ + | jq -r '.result // empty' 2>/dev/null) + [ -z "$block0" ] && return 1 + curl -s --max-time "$TIMEOUT" -H "Content-Type: application/json" \ + -d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"chain_getHeader\",\"params\":[\"$block0\"]}" "$rpc" 2>/dev/null \ + | jq -r '.result.stateRoot // empty' 2>/dev/null +} + +# Refresh a spec from its live chain. +# +# Always sets genesis.stateRootHash from the chain's block 0, so the spec keeps matching the chain's +# genesis after a wipe. smoldot derives the block-announces protocol name from the genesis hash, so +# a stale genesis yields a name no peer offers, the substream fails with ProtocolNotAvailable, and +# smoldot can't sync the chain. sync_state_genSyncSpec is not used for the genesis, as it returns a +# genesis that serializes extra storage keys, so its computed hash does not match the real block 0. +# +# For a relay it also fetches sync_state_genSyncSpec and writes a fresh lightSyncState checkpoint +# for smoldot to warp-sync from (a relay has no parent to follow). A parachain follows its relay +# instead, so any committed lightSyncState is dropped. If that response carries bootNodes, they are +# health-checked and pruned to the reachable ones; otherwise existing bootNodes are preserved. +# +# Pass one or more RPC URLs; the first that serves block 0 is used. +refresh_spec() { + local spec_file="$1" + local is_relay="$2" + shift 2 + + echo "Refreshing $spec_file..." + + local fields="" rpc="" state_root="" peers=() + for candidate in "$@"; do + if [ -n "$state_root" ]; then + peers+=("$candidate") + continue + fi + state_root=$(fetch_state_root "$candidate") || true + if [ -n "$state_root" ]; then + rpc="$candidate" + continue + fi + echo " No block 0 from $candidate" + done + if [ -z "$state_root" ]; then + echo " ERROR: Could not fetch genesis state root for $spec_file from any RPC." + return 1 + fi + fields+="genesis.stateRootHash" + + # Every reachable endpoint must agree on the genesis, so one endpoint alone cannot redirect a + # spec at a different chain. + for peer in "${peers[@]:-}"; do + [ -z "$peer" ] && continue + local peer_root + peer_root=$(fetch_state_root "$peer") || true + if [ -n "$peer_root" ] && [ "$peer_root" != "$state_root" ]; then + echo " ERROR: $peer serves genesis state root $peer_root, $rpc serves $state_root." + INTEGRITY_FAILURES=$((INTEGRITY_FAILURES + 1)) + return 1 + fi + done + + # Relays read sync_state_genSyncSpec for their checkpoint; the same response also carries the + # bootNodes. Pull only those two fields; jq drops the multi-MB genesis the response also returns. + local light_sync_state="null" bootnodes="[]" + if [ "$is_relay" = "true" ]; then + local fresh + fresh=$(curl -s --max-time "$TIMEOUT" -H "Content-Type: application/json" \ + -d '{"id":1,"jsonrpc":"2.0","method":"sync_state_genSyncSpec","params":[true]}' "$rpc" 2>/dev/null \ + | jq -c '{lightSyncState: .result.lightSyncState, bootNodes: .result.bootNodes}' 2>/dev/null || echo "null") + light_sync_state=$(echo "$fresh" | jq -c '.lightSyncState // null') + bootnodes=$(echo "$fresh" | jq -c '.bootNodes // []') + # Without lightSyncState, smoldot can't sync a relay from a stateRootHash-only genesis, so fail. + if [ "$light_sync_state" = "null" ]; then + echo " ERROR: Could not fetch lightSyncState from $rpc." + return 1 + fi + # The checkpoint is what the light client trusts instead of syncing from genesis, so a second + # endpoint has to agree on the block it pins before it is compiled into the wasm. + local checkpoint_header + checkpoint_header=$(echo "$light_sync_state" | jq -r '.finalizedBlockHeader // .finalized_block_header // empty') + if [ -z "$checkpoint_header" ]; then + echo " ERROR: lightSyncState from $rpc carries no finalized block header." + return 1 + fi + local checkpoint_authorities + checkpoint_authorities=$(echo "$light_sync_state" | jq -r '.grandpaAuthoritySet // empty') + if ! CHECKPOINT_HEADER="$checkpoint_header" \ + CHECKPOINT_AUTHORITY_SET="$checkpoint_authorities" \ + PEER_RPCS="$(printf '%s\n' "${peers[@]:-}" | jq -R . | jq -sc 'map(select(length > 0))')" \ + TIMEOUT="$TIMEOUT" node -e "$CHECKPOINT_JS"; then + echo " ERROR: the checkpoint from $rpc could not be corroborated; leaving $spec_file alone." + INTEGRITY_FAILURES=$((INTEGRITY_FAILURES + 1)) + return 1 + fi + fields+=" + lightSyncState" + fi + + # lightSyncState can be hundreds of KB, so it goes via stdin; the small state root goes via env. + if ! echo "$light_sync_state" | STATE_ROOT="$state_root" \ + node -e ' + const fs = require("fs"); + let stdin = ""; + process.stdin.on("data", (chunk) => stdin += chunk); + process.stdin.on("end", () => { + const specPath = process.argv[1]; + const spec = JSON.parse(fs.readFileSync(specPath, "utf8")); + spec.genesis = { stateRootHash: process.env.STATE_ROOT }; + const lss = JSON.parse(stdin); + if (lss) spec.lightSyncState = lss; + else delete spec.lightSyncState; + fs.writeFileSync(specPath, JSON.stringify(spec)); + }); + ' "$SPECS_DIR/$spec_file"; then + echo " ERROR: failed to write $spec_file; leaving it alone." + INTEGRITY_FAILURES=$((INTEGRITY_FAILURES + 1)) + return 1 + fi + + # Health-check bootNodes only when the chain actually advertises some. A failure + # here leaves the already-written genesis and checkpoint in place, so it is + # reported without discarding them. + if [ "$(echo "$bootnodes" | jq 'length')" -gt 0 ]; then + if BOOTNODES="$bootnodes" node -e "$BOOTNODES_JS" "$SPECS_DIR/$spec_file"; then + fields+=" + bootNodes" + else + echo " WARNING: the bootnode health check failed; $spec_file keeps its existing bootnodes." + fi + fi + + echo " Updated $spec_file: $fields" + echo "" +} + +# A single network's outage should not block refreshing the others, so each call is non-fatal. + +# Paseo Next v2 (the network dotli production runs on). +refresh_spec "paseo.json" true "https://paseo-rpc.n.dwellir.com" \ + "https://rpc.interweb-it.com/paseo" \ + "https://rpc-paseo.stakeworld.io" || true +refresh_spec "paseo-next-v2-asset-hub.json" false "https://paseo-asset-hub-next-rpc.polkadot.io" || true +refresh_spec "paseo-next-v2-bulletin.json" false "https://paseo-bulletin-next-rpc.polkadot.io" || true +refresh_spec "paseo-next-v2-people.json" false "https://paseo-people-next-system-rpc.polkadot.io" || true + +# Previewnet. +refresh_spec "previewnet.json" true "https://previewnet.substrate.dev/relay/alice" \ + "https://previewnet.substrate.dev/relay/bob" \ + "https://previewnet.substrate.dev/relay/charlie" || true +refresh_spec "previewnet-asset-hub.json" false "https://previewnet.substrate.dev/asset-hub" || true +refresh_spec "previewnet-bulletin.json" false "https://previewnet.substrate.dev/bulletin" || true +refresh_spec "previewnet-people.json" false "https://previewnet.substrate.dev/people" || true + +if [ "$INTEGRITY_FAILURES" -gt 0 ]; then + echo "FAILED: $INTEGRITY_FAILURES spec(s) could not be trusted, either because endpoints" + echo "disagreed about the chain they serve or because no second endpoint could corroborate the" + echo "checkpoint. Those specs were left unchanged. Investigate before publishing this run." + exit 1 +fi + +echo "Done. Bundled chain specs updated in rust/crates/truapi-provider/networks/" +echo "Publish a new @parity/truapi-provider so consumers pick up the refresh." diff --git a/settings.gradle.kts b/settings.gradle.kts index 4fb21d1c1..ecf97c1bd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,3 +28,6 @@ rootProject.name = "truapi" include(":truapi-host") project(":truapi-host").projectDir = file("android/truapi-host") + +include(":truapi-provider") +project(":truapi-provider").projectDir = file("android/truapi-provider")