From 3780d7d48bdc96db6b0df6f3eb955015e1779a60 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 26 Mar 2021 11:05:58 +0300 Subject: [PATCH 001/113] Refactor and sync using p2p protocol --- .dockerignore | 14 +- .github/workflows/rust.yml | 23 +- .gitignore | 5 +- Cargo.lock | 1058 ++++++++--------- Cargo.toml | 46 +- Dockerfile | 55 +- contrib/addr.py | 8 +- contrib/client.py | 1 + contrib/get_tip.py | 16 + contrib/get_tx.py | 16 + contrib/health_check.py | 16 + contrib/tx_fee.py | 36 +- doc/schema.md | 28 +- doc/usage.md | 297 ++++- examples/compact.rs | 29 - examples/index.rs | 42 - examples/txid_collisions.rs | 49 - internal/README.md | 3 + .../config_specification.toml | 32 +- query.sh | 15 + server.sh | 16 + src/app.rs | 60 - src/bin/electrs.rs | 96 +- src/bin/query.rs | 43 + src/bin/sync.rs | 9 + src/bulk.rs | 289 ----- src/cache.rs | 287 +---- src/chain.rs | 130 ++ src/config.rs | 200 +--- src/daemon.rs | 870 ++++---------- src/db.rs | 276 +++++ src/electrum.rs | 321 +++++ src/errors.rs | 17 - src/fake.rs | 37 - src/index.rs | 571 +++------ src/lib.rs | 50 +- src/map.rs | 77 ++ src/mempool.rs | 420 +++---- src/merkle.rs | 79 ++ src/metrics.rs | 227 ++-- src/notify.rs | 70 -- src/query.rs | 580 --------- src/rpc.rs | 636 ---------- src/server.rs | 201 ++++ src/signal.rs | 52 - src/signals.rs | 30 + src/status.rs | 365 ++++++ src/store.rs | 193 --- src/tests/fixtures/incomplete_block.hex | 1 - src/tracker.rs | 102 ++ src/types.rs | 303 +++++ src/util.rs | 408 ------- sync.sh | 15 + tests/run.sh | 102 ++ 54 files changed, 3818 insertions(+), 5104 deletions(-) create mode 100755 contrib/get_tip.py create mode 100755 contrib/get_tx.py create mode 100755 contrib/health_check.py delete mode 100644 examples/compact.rs delete mode 100644 examples/index.rs delete mode 100644 examples/txid_collisions.rs create mode 100644 internal/README.md rename config_spec.toml => internal/config_specification.toml (77%) create mode 100755 query.sh create mode 100755 server.sh delete mode 100644 src/app.rs create mode 100644 src/bin/query.rs create mode 100644 src/bin/sync.rs delete mode 100644 src/bulk.rs create mode 100644 src/chain.rs create mode 100644 src/db.rs create mode 100644 src/electrum.rs delete mode 100644 src/errors.rs delete mode 100644 src/fake.rs create mode 100644 src/map.rs create mode 100644 src/merkle.rs delete mode 100644 src/notify.rs delete mode 100644 src/query.rs delete mode 100644 src/rpc.rs create mode 100644 src/server.rs delete mode 100644 src/signal.rs create mode 100644 src/signals.rs create mode 100644 src/status.rs delete mode 100644 src/store.rs delete mode 100644 src/tests/fixtures/incomplete_block.hex create mode 100644 src/tracker.rs create mode 100644 src/types.rs delete mode 100644 src/util.rs create mode 100755 sync.sh create mode 100755 tests/run.sh diff --git a/.dockerignore b/.dockerignore index d5348a7..30f8bb2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,11 @@ -target/ -.git/ -_*/ +.* +_* +contrib +db* +dist +doc +Dockerfile +examples +scripts +target +tests diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 55d751b..b803d59 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,22 +1,15 @@ -name: Rust +name: electrs on: [push, pull_request] jobs: - + electrs: + name: Electrum Integration Test runs-on: ubuntu-latest - steps: - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - profile: minimal - override: true - - uses: actions/checkout@v1 - - name: Check - run: cargo check + - name: Checkout + uses: actions/checkout@v2 - name: Build - run: cargo build - - name: Run tests - run: cargo test + run: docker build . --rm -t electrs:tests + - name: Test + run: docker run -v $PWD/contrib/:/contrib -v $PWD/tests/:/tests --rm electrs:tests bash /tests/run.sh diff --git a/.gitignore b/.gitignore index 7a49406..62a7a33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ target -*db/ +/db*/ _*/ *.log *.sublime* @@ -8,3 +8,6 @@ _*/ .env *.dat electrs.toml +data/ +tests/bitcoin-* +tests/bin diff --git a/Cargo.lock b/Cargo.lock index 39aa274..5308e07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,14 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "addr2line" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" -dependencies = [ - "gimli", -] - [[package]] name = "adler" version = "0.2.3" @@ -17,135 +8,82 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" [[package]] name = "aho-corasick" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ "memchr", ] [[package]] -name = "ansi_term" -version = "0.11.0" +name = "anyhow" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "arc-swap" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" - -[[package]] -name = "ascii" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97be891acc47ca214468e09425d02cef3af2c94d0d82081cd02061f996802f14" +checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4" [[package]] name = "atty" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ + "hermit-abi", "libc", - "termion", - "winapi 0.3.9", + "winapi", ] +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "backtrace" -version = "0.3.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" -version = "0.10.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" dependencies = [ "byteorder", + "safemem", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "bech32" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" -[[package]] -name = "bincode" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" -dependencies = [ - "byteorder", - "serde", -] - [[package]] name = "bindgen" -version = "0.47.3" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df683a55b54b41d5ea8ebfaebb5aa7e6b84e3f3006a78f010dadc9ca88469260" +checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" dependencies = [ "bitflags", "cexpr", - "cfg-if", "clang-sys", - "clap", - "env_logger", - "hashbrown 0.1.8", - "lazy_static 1.4.0", - "log", + "lazy_static", + "lazycell", "peeking_take_while", - "proc-macro2 0.4.30", - "quote 0.6.13", + "proc-macro2", + "quote", "regex", - "which", + "rustc-hash", + "shlex", ] [[package]] name = "bitcoin" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6558aeb12c290cce541a222cba280387161f2bd180a7feb85f8f172cd8ba80e8" +checksum = "1ec5f88a446d66e7474a3b8fa2e348320b574463fb78d799d90ba68f79f48e0e" dependencies = [ "bech32", "bitcoin_hashes", @@ -155,30 +93,44 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.8.0" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0ab784be052cc1e915a78b8aaf5101eebbc2d0ab2b6f5124985f3677ae2bea" +checksum = "0aaf87b776808e26ae93289bc7d025092b6d909c193f0cdee0b3a86e7bd3c776" dependencies = [ "serde", ] +[[package]] +name = "bitcoincore-rpc" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d708433972bf78bd5f909d1d288f9ac1cceeab1460edb954e962f83e1f440a3" +dependencies = [ + "bitcoincore-rpc-json", + "jsonrpc", + "log 0.4.11", + "serde", + "serde_json", +] + +[[package]] +name = "bitcoincore-rpc-json" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977e55a945ab1e3c446dea93267876703c15e07c7d6eeb1dfa1766b3190c560f" +dependencies = [ + "bitcoin", + "hex 0.3.2", + "serde", + "serde_json", +] + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "blake2b_simd" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - [[package]] name = "byteorder" version = "1.3.4" @@ -193,23 +145,23 @@ checksum = "513d17226888c7b8283ac02a1c1b0d8a9d4cbf6db65dfadb79f598f5d7966fe9" dependencies = [ "serde", "serde_derive", - "toml 0.5.6", + "toml", ] [[package]] name = "cc" -version = "1.0.60" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" +checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" dependencies = [ "jobserver", ] [[package]] name = "cexpr" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" dependencies = [ "nom", ] @@ -221,209 +173,187 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] -name = "chrono" -version = "0.4.18" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d021fddb7bd3e734370acfa4a83f34095571d8570c039f1420d77540f68d5772" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time", - "winapi 0.3.9", -] - -[[package]] -name = "chunked_transfer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "0.26.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef0c1bcf2e99c649104bd7a7012d8f8802684400e03db0ec0af48583c6fa0e4" +checksum = "0659001ab56b791be01d4b729c44376edc6718cf389a502e579b77b758f3296c" dependencies = [ - "glob 0.2.11", + "glob", "libc", "libloading", ] [[package]] -name = "clap" -version = "2.33.3" +name = "cloudabi" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "ansi_term", - "atty", "bitflags", - "strsim", - "textwrap", - "unicode-width", - "vec_map", ] [[package]] name = "configure_me" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177561486192921b103fef5c54d25ded8bb6ba86e2ef4d12e0aa5aba3233a9fb" +checksum = "d03c1fbdead926855bdafee8ddf16cd42efb3c75d8cde8c87f8937b99510b39d" dependencies = [ "parse_arg", "serde", "serde_derive", - "toml 0.4.10", + "toml", ] [[package]] name = "configure_me_codegen" -version = "0.3.14" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a76c3f66dc3e41f12807d6964efcd66eb38ab602ea11eb776005eb3deb875c" +checksum = "97f64541226ea2aaaad89ce86ec9453b1e913a54d71eb987ab9d8ed52dacce2f" dependencies = [ "cargo_toml", "fmt2io", "man", "serde", "serde_derive", - "toml 0.4.10", + "toml", "unicode-segmentation", "void", ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "crc32fast" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] [[package]] name = "crossbeam-channel" -version = "0.3.9" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ - "crossbeam-utils 0.6.6", + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.6.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ - "cfg-if", - "lazy_static 1.4.0", + "autocfg 1.0.1", + "cfg-if 1.0.0", + "lazy_static", ] [[package]] -name = "crossbeam-utils" -version = "0.7.2" +name = "dirs-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "autocfg", - "cfg-if", - "lazy_static 1.4.0", + "cfg-if 1.0.0", + "dirs-sys-next", ] [[package]] -name = "dirs" -version = "1.0.5" +name = "dirs-sys-next" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" dependencies = [ "libc", "redox_users", - "winapi 0.3.9", + "winapi", ] +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "electrs" -version = "0.8.5" +version = "0.9.0" dependencies = [ - "base64 0.10.1", - "bincode", + "anyhow", "bitcoin", + "bitcoincore-rpc", "configure_me", "configure_me_codegen", "crossbeam-channel", - "dirs", - "error-chain", - "glob 0.3.0", - "hex", - "libc", - "log", - "lru", - "num_cpus", - "page_size", + "dirs-next", + "env_logger", + "hyper", + "log 0.4.11", "prometheus", - "protobuf", + "rayon", "rocksdb", "rust-crypto", "serde", "serde_derive", "serde_json", "signal-hook", - "stderrlog", - "sysconf", - "time", - "tiny_http", ] [[package]] name = "env_logger" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime", - "log", + "log 0.4.11", "regex", "termcolor", ] [[package]] -name = "errno" -version = "0.2.6" +name = "flate2" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eab5ee3df98a279d9b316b1af6ac95422127b1290317e6d18c1743c99418b01" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ - "errno-dragonfly", + "cfg-if 1.0.0", + "crc32fast", "libc", - "winapi 0.3.9", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" -dependencies = [ - "gcc", - "libc", -] - -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "backtrace", - "version_check 0.9.2", -] - -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", + "miniz_oxide", ] [[package]] @@ -452,54 +382,26 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "getrandom" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] -[[package]] -name = "gimli" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" - -[[package]] -name = "glob" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" - [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "hashbrown" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" -dependencies = [ - "byteorder", - "scopeguard", -] - -[[package]] -name = "hashbrown" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353" - [[package]] name = "hermit-abi" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ "libc", ] @@ -510,6 +412,18 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + [[package]] name = "humantime" version = "1.3.0" @@ -519,6 +433,25 @@ dependencies = [ "quick-error", ] +[[package]] +name = "hyper" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +dependencies = [ + "base64", + "httparse", + "language-tags", + "log 0.3.9", + "mime", + "num_cpus", + "time", + "traitobject", + "typeable", + "unicase", + "url", +] + [[package]] name = "idna" version = "0.1.5" @@ -530,6 +463,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "itoa" version = "0.4.6" @@ -546,20 +488,22 @@ dependencies = [ ] [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "jsonrpc" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +checksum = "436f3455a8a4e9c7b14de9f1206198ee5d0bdc2db1b560339d2141093d7dd389" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "hyper", + "serde", + "serde_derive", + "serde_json", ] [[package]] -name = "lazy_static" -version = "0.2.11" +name = "language-tags" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" [[package]] name = "lazy_static" @@ -568,49 +512,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "libc" -version = "0.2.77" +name = "lazycell" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "libloading" -version = "0.5.2" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc" dependencies = [ - "cc", - "winapi 0.3.9", + "cfg-if 1.0.0", + "winapi", ] [[package]] name = "librocksdb-sys" -version = "5.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19778314deaa7048f2ea7d07b8aa12e1c227acebe975a37eeab6d2f8c74e41b" +version = "6.17.3" +source = "git+https://github.com/romanz/rust-rocksdb?rev=379c2d7f2a15fe31d2ec2726f4b6179de5c8c287#379c2d7f2a15fe31d2ec2726f4b6179de5c8c287" dependencies = [ "bindgen", "cc", - "glob 0.2.11", + "glob", "libc", ] +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.11", +] + [[package]] name = "log" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if", -] - -[[package]] -name = "lru" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8f669d42c72d18514dfca8115689c5f6370a17d980cb5bd777a67f404594c8" -dependencies = [ - "hashbrown 0.5.0", + "cfg-if 0.1.10", ] [[package]] @@ -630,47 +588,46 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "memoffset" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg 1.0.1", +] + +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] [[package]] name = "miniz_oxide" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" dependencies = [ "adler", - "autocfg", + "autocfg 1.0.1", ] [[package]] name = "nom" -version = "4.2.3" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ "memchr", - "version_check 0.1.5", -] - -[[package]] -name = "num-integer" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" -dependencies = [ - "autocfg", + "version_check 0.9.2", ] [[package]] @@ -684,25 +641,28 @@ dependencies = [ ] [[package]] -name = "numtoa" -version = "0.1.0" +name = "parking_lot" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - -[[package]] -name = "object" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" - -[[package]] -name = "page_size" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0" +dependencies = [ + "cfg-if 1.0.0", + "instant", "libc", - "winapi 0.3.9", + "redox_syscall", + "smallvec", + "winapi", ] [[package]] @@ -725,34 +685,42 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "proc-macro2" -version = "0.4.30" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] -name = "proc-macro2" -version = "1.0.23" +name = "procfs" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ef7cd2518ead700af67bf9d1a658d90b6037d77110fd9c0445429d0ba1c6c9" +checksum = "ab8809e0c18450a2db0f236d2a44ec0b4c1412d0eb936233579f0990faa5d5cd" dependencies = [ - "unicode-xid 0.2.1", + "bitflags", + "byteorder", + "flate2", + "hex 0.4.2", + "lazy_static", + "libc", ] [[package]] name = "prometheus" -version = "0.5.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e3f33ff50a88c73ad8458fa6c22931aa7a6e19bb4a95d62816618c153b3f02" +checksum = "c8425533e7122f0c3cc7a37e6244b16ad3a2cc32ae7ac6276e2a75da0d9c200d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fnv", - "lazy_static 1.4.0", + "lazy_static", + "libc", + "parking_lot", + "procfs", "protobuf", - "quick-error", - "spin", + "regex", + "thiserror", ] [[package]] @@ -767,22 +735,13 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - [[package]] name = "quote" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "proc-macro2 1.0.23", + "proc-macro2", ] [[package]] @@ -805,7 +764,36 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi 0.3.9", + "winapi", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", ] [[package]] @@ -823,6 +811,93 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg 1.0.1", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -838,15 +913,6 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" -[[package]] -name = "redox_termios" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -dependencies = [ - "redox_syscall", -] - [[package]] name = "redox_users" version = "0.3.5" @@ -855,32 +921,30 @@ checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ "getrandom", "redox_syscall", - "rust-argon2", ] [[package]] name = "regex" -version = "1.3.9" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local 1.0.1", + "thread_local", ] [[package]] name = "regex-syntax" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" [[package]] name = "rocksdb" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29e12aab379a49bfbca337132440be73d1de6f328d5635641c2b28ac9dfe514" +version = "0.15.0" +source = "git+https://github.com/romanz/rust-rocksdb?rev=379c2d7f2a15fe31d2ec2726f4b6179de5c8c287#379c2d7f2a15fe31d2ec2726f4b6179de5c8c287" dependencies = [ "libc", "librocksdb-sys", @@ -892,18 +956,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33e4fb37ba46888052c763e4ec2acfedd8f00f62897b630cadb6298b833675e" -[[package]] -name = "rust-argon2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" -dependencies = [ - "base64 0.12.3", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils 0.7.2", -] - [[package]] name = "rust-crypto" version = "0.2.36" @@ -918,10 +970,10 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.16" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-serialize" @@ -936,52 +988,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] -name = "scopeguard" +name = "safemem" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "secp256k1" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3f534ef4e9dfa6c4b2ccca131f791daddf9cc2123b4124615cb144ea91611a" +checksum = "fcfd0eece5bc8fca7ca07a0c17ffd334b028f72ffbe9dd8ec924a8c885753278" dependencies = [ + "rand 0.6.5", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71453d9b088b19ae43a803d88ecaa876b11ed8999df024cca4becb6be9aee351" +checksum = "67e4b6455ee49f5901c8985b88f98fb0a0e1d90a6661f5a03f4888bd987dad29" dependencies = [ "cc", ] [[package]] name = "serde" -version = "1.0.116" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" -version = "1.0.116" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ - "proc-macro2 1.0.23", - "quote 1.0.7", + "proc-macro2", + "quote", "syn", ] [[package]] name = "serde_json" -version = "1.0.57" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" dependencies = [ "itoa", "ryu", @@ -989,10 +1051,16 @@ dependencies = [ ] [[package]] -name = "signal-hook" -version = "0.1.16" +name = "shlex" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "signal-hook" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa894ef3fade0ee7243422f4fbbd6c2b48e6de767e621d37ef65f2310f53cea" dependencies = [ "libc", "signal-hook-registry", @@ -1000,100 +1068,57 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" dependencies = [ - "arc-swap", "libc", ] [[package]] -name = "spin" -version = "0.4.10" +name = "smallvec" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" - -[[package]] -name = "stderrlog" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" -dependencies = [ - "atty", - "chrono", - "log", - "termcolor", - "thread_local 0.3.4", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" [[package]] name = "syn" -version = "1.0.42" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228" +checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" dependencies = [ - "proc-macro2 1.0.23", - "quote 1.0.7", - "unicode-xid 0.2.1", -] - -[[package]] -name = "sysconf" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e93f5d45535f49b6a05ef7ac2f0f795d28de494cf53a512751602c9849bea3" -dependencies = [ - "errno", - "kernel32-sys", - "libc", - "winapi 0.2.8", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "termcolor" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ "winapi-util", ] [[package]] -name = "termion" -version = "1.5.5" +name = "thiserror" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" dependencies = [ - "libc", - "numtoa", - "redox_syscall", - "redox_termios", + "thiserror-impl", ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "thiserror-impl" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" dependencies = [ - "unicode-width", -] - -[[package]] -name = "thread_local" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" -dependencies = [ - "lazy_static 0.2.11", - "unreachable", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1102,7 +1127,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", ] [[package]] @@ -1113,44 +1138,52 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", -] - -[[package]] -name = "tiny_http" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1661fa0a44c95d01604bd05c66732a446c657efb62b5164a7a083a3b552b4951" -dependencies = [ - "ascii", - "chrono", - "chunked_transfer", - "log", - "url", + "winapi", ] [[package]] name = "tinyvec" -version = "0.3.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" +checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml" -version = "0.4.10" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ "serde", ] [[package]] -name = "toml" -version = "0.5.6" +name = "traitobject" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" + +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" dependencies = [ - "serde", + "version_check 0.1.5", ] [[package]] @@ -1164,30 +1197,18 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.13" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" - -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" [[package]] name = "unicode-xid" @@ -1195,15 +1216,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] - [[package]] name = "url" version = "1.7.2" @@ -1215,12 +1227,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.1.5" @@ -1251,22 +1257,6 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" -[[package]] -name = "which" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164" -dependencies = [ - "failure", - "libc", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -1277,12 +1267,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1295,7 +1279,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3951308..aeedefc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "electrs" -version = "0.8.5" +version = "0.9.0" authors = ["Roman Zeyde "] description = "An efficient re-implementation of Electrum Server in Rust" license = "MIT" @@ -12,40 +12,30 @@ readme = "README.md" edition = "2018" build = "build.rs" -[package.metadata.configure_me] -spec = "config_spec.toml" +[features] +default = ["rocksdb/zstd"] -[profile.release] -lto = true +[package.metadata.configure_me] +spec = "internal/config_specification.toml" [dependencies] -base64 = "0.10" -bincode = "1.0" -bitcoin = { version = "0.24", features = ["use-serde"] } -configure_me = "0.3.4" -configure_me_codegen = "0.3.14" -crossbeam-channel = "0.3" -dirs = "1.0" -error-chain = "0.12" -glob = "0.3" -hex = "0.3" -libc = "0.2" +anyhow = "1.0" +bitcoin = { version = "0.26", features = ["use-serde", "rand"] } +bitcoincore-rpc = "0.13" +configure_me = "0.4" +crossbeam-channel = "0.5" +dirs-next = "2.0" +env_logger = "0.7" +hyper = "0.10" log = "0.4" -lru = "0.1" -num_cpus = "1.0" -page_size = "0.4" -prometheus = "0.5" -protobuf = "= 2.14.0" # https://github.com/stepancheg/rust-protobuf/blob/master/CHANGELOG.md#2150---2020-06-21 -rocksdb = "= 0.12.2" # due to https://github.com/romanz/electrs/issues/193 +prometheus = { version = "0.11", features = ["process"] } +rayon = "1.5" +rocksdb = { git = "https://github.com/romanz/rust-rocksdb", rev = "379c2d7f2a15fe31d2ec2726f4b6179de5c8c287", default-features = false } # to support building with Rust 1.41.1 rust-crypto = "0.2" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" -signal-hook = "0.1" -stderrlog = "0.4.1" -sysconf = ">=0.3.4" -time = "0.1" -tiny_http = "0.6" +signal-hook = "0.3" [build-dependencies] -configure_me_codegen = "0.3.12" +configure_me_codegen = "0.4" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ebfac60..e3c590c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,42 @@ -FROM rust:1.44.1-slim-buster +### Electrum Rust Server ### +FROM rust:1.48.0-slim as electrs-build +RUN apt-get update +RUN apt-get install -qq -y clang cmake +RUN rustup component add rustfmt -RUN apt-get update \ - && apt-get install -y --no-install-recommends clang=1:7.* cmake=3.* \ - libsnappy-dev=1.* \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +# Build, test and install electrs +WORKDIR /build/electrs +COPY . . +RUN cargo fmt -- --check +RUN cargo build --locked --release --all +RUN cargo test --locked --release --all +RUN cargo install --locked --path . -RUN adduser --disabled-login --system --shell /bin/false --uid 1000 user +FROM debian:buster-slim as updated +RUN apt-get update -qqy -WORKDIR /home/user -COPY ./ /home/user -RUN chown -R user . +### Bitcoin Core ### +FROM updated as bitcoin-build +# Download +RUN apt-get install -qqy wget +WORKDIR /build/bitcoin +RUN wget -q https://bitcoincore.org/bin/bitcoin-core-0.21.0/bitcoin-0.21.0-x86_64-linux-gnu.tar.gz +RUN tar xvf bitcoin-0.21.0-x86_64-linux-gnu.tar.gz +RUN mv -v bitcoin-0.21.0/bin/bitcoind . +RUN mv -v bitcoin-0.21.0/bin/bitcoin-cli . -USER user +FROM updated as result +# Copy the binaries +COPY --from=electrs-build /usr/local/cargo/bin/electrs /usr/bin/electrs +COPY --from=bitcoin-build /build/bitcoin/bitcoind /build/bitcoin/bitcoin-cli /usr/bin/ +RUN bitcoind -version && bitcoin-cli -version -RUN cargo install --path . +### Electrum ### +# Clone latest Electrum wallet and a few test tools +WORKDIR /build/ +RUN apt-get install -qqy git libsecp256k1-0 python3-cryptography python3-setuptools python3-pip jq +RUN git clone --recurse-submodules https://github.com/spesmilo/electrum/ && cd electrum/ && git log -1 +RUN python3 -m pip install -e electrum/ -# Electrum RPC -EXPOSE 50001 - -# Prometheus monitoring -EXPOSE 4224 - -STOPSIGNAL SIGINT +RUN electrum version --offline +WORKDIR / diff --git a/contrib/addr.py b/contrib/addr.py index c1f7cdf..5e8bae7 100755 --- a/contrib/addr.py +++ b/contrib/addr.py @@ -22,9 +22,13 @@ def main(): for addr in args.address: script = network.parse.address(addr).script() script_hash = hashlib.sha256(script).digest()[::-1].hex() - reply = conn.call('blockchain.scripthash.get_balance', script_hash) + reply = conn.call('blockchain.scripthash.subscribe', script_hash) + print(f'{reply}') + reply = conn.call('blockchain.scripthash.get_history', script_hash) result = reply['result'] - print('{} has {} satoshis'.format(addr, result)) + print(f'{addr} has {len(result)} transactions:') + for tx in result: + print(f'* {tx["tx_hash"]}') if __name__ == '__main__': diff --git a/contrib/client.py b/contrib/client.py index 17ce913..145d728 100644 --- a/contrib/client.py +++ b/contrib/client.py @@ -12,6 +12,7 @@ class Client: 'id': self.id, 'method': method, 'params': list(args), + 'jsonrpc': '2.0', } msg = json.dumps(req) + '\n' self.s.sendall(msg.encode('ascii')) diff --git a/contrib/get_tip.py b/contrib/get_tip.py new file mode 100755 index 0000000..bb2b517 --- /dev/null +++ b/contrib/get_tip.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import argparse +import client +import json + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("host") + parser.add_argument("port", type=int) + args = parser.parse_args() + + conn = client.Client((args.host, args.port)) + print(json.dumps(conn.call("blockchain.headers.subscribe")["result"])) + +if __name__ == '__main__': + main() diff --git a/contrib/get_tx.py b/contrib/get_tx.py new file mode 100755 index 0000000..af8760b --- /dev/null +++ b/contrib/get_tx.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import argparse +import client +import json + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("txid") + args = parser.parse_args() + + conn = client.Client(("localhost", 50001)) + tx = conn.call("blockchain.transaction.get", args.txid, True)["result"] + print(json.dumps(tx)) + +if __name__ == "__main__": + main() diff --git a/contrib/health_check.py b/contrib/health_check.py new file mode 100755 index 0000000..bb7f1be --- /dev/null +++ b/contrib/health_check.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import argparse +import client +import json + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("host") + parser.add_argument("port", type=int) + args = parser.parse_args() + + conn = client.Client((args.host, args.port)) + print(json.dumps(conn.call("server.version", "health_check", "1.4")["result"])) + +if __name__ == '__main__': + main() diff --git a/contrib/tx_fee.py b/contrib/tx_fee.py index 7af4b22..05ae2fb 100755 --- a/contrib/tx_fee.py +++ b/contrib/tx_fee.py @@ -1,33 +1,23 @@ #!/usr/bin/env python3 import argparse -import daemon - +import client def main(): parser = argparse.ArgumentParser() - parser.add_argument('txid') + parser.add_argument("txid") args = parser.parse_args() - d = daemon.Daemon(port=8332, cookie_dir='~/.bitcoin') - txid = args.txid + conn = client.Client(("localhost", 50001)) + tx = conn.call("blockchain.transaction.get", args.txid, True)["result"] + fee = 0 + for vin in tx["vin"]: + prev_txid = vin["txid"] + prev_tx = conn.call("blockchain.transaction.get", prev_txid, True)["result"] + txo = prev_tx["vout"][vin["vout"]] + fee += txo["value"] + fee -= sum(vout["value"] for vout in tx["vout"]) - txn, = d.request('getrawtransaction', [[txid, True]]) - vin = txn['vin'] + print(f'vSize = {tx["vsize"]}, Fee = {1e3 * fee:.2f} mBTC = {1e8 * fee / tx["vsize"]:.2f} sat/vB') - fee = 0.0 - for txi in txn['vin']: - prev_txid = txi['txid'] - prev_tx, = d.request('getrawtransaction', [[prev_txid, True]]) - index = txi['vout'] - prev_txo = prev_tx['vout'][index] - print(f"{prev_txid}:{index:<5} {prev_txo['value']:+20.8f}") - fee += prev_txo['value'] - - for i, txo in enumerate(txn['vout']): - print(f"{txid}:{i:<5} {-txo['value']:+20.8f}") - fee -= txo['value'] - - print(f"Fee = {1e6 * fee:.2f} uBTC = {1e8 * fee / txn['vsize']:.2f} sat/vB") - -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/doc/schema.md b/doc/schema.md index 1e1363c..8397748 100644 --- a/doc/schema.md +++ b/doc/schema.md @@ -1,31 +1,31 @@ # Index Schema -The index is stored at a single RocksDB database using the following schema: +The index is stored at a single RocksDB database using the following column families: -## Transaction outputs' index +## Transaction outputs' index (`funding`) Allows efficiently finding all funding transactions for a specific address: -| Code | Script Hash Prefix | Funding TxID Prefix | | -| ------ | -------------------- | --------------------- | - | -| `b'O'` | `SHA256(script)[:8]` | `txid[:8]` | | +| Script Hash Prefix | Confirmed Block Height | +| -------------------- | ---------------------- | +| `SHA256(script)[:8]` | `height as u32` | -## Transaction inputs' index +## Transaction inputs' index (`spending`) Allows efficiently finding spending transaction of a specific output: -| Code | Funding TxID Prefix | Funding Output Index | Spending TxID Prefix | | -| ------ | -------------------- | --------------------- | --------------------- | - | -| `b'I'` | `txid[:8]` | `uint16` | `txid[:8]` | | +| Previous Outpoint Prefix | Confirmed Block Height | +| ------------------------ | ---------------------- | +| `txid[:8] as u64 + vout` | `height as u32` | -## Full Transaction IDs +## Transaction ID index (`txid`) -In order to save storage space, we store the full transaction IDs once, and use their 8-byte prefixes for the indexes above. +In order to save storage space, we map the 8-byte transaction ID prefix to its confirmed block height: -| Code | Transaction ID | | Confirmed height | -| ------ | ----------------- | - | ------------------ | -| `b'T'` | `txid` (32 bytes) | | `uint32` | +| Txid Prefix | Confirmed height | +| ----------- | ---------------- | +| `txid[:8]` | `height as u32` | Note that this mapping allows us to use `getrawtransaction` RPC to retrieve actual transaction data from without `-txindex` enabled (by explicitly specifying the [blockhash](https://github.com/bitcoin/bitcoin/commit/497d0e014cc79d46531d570e74e4aeae72db602d)). diff --git a/doc/usage.md b/doc/usage.md index 44c88f5..d29c4d3 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -4,66 +4,201 @@ ### Build dependencies -Install [recent Rust](https://rustup.rs/) (1.41.1+, `apt install cargo` is preferred for Debian 10), -[latest Bitcoin Core](https://bitcoincore.org/en/download/) (0.16+) -and [latest Electrum wallet](https://electrum.org/#download) (3.3+). +Note for Raspberry Pi 4 owners: the old versions of OS/toolchains produce broken binaries. +Make sure to use latest OS! (see #226) -Also, install the following packages (on Debian): +Install [recent Rust](https://rustup.rs/) (1.48.0+, `apt install cargo` is preferred for Debian 10), +[latest Bitcoin Core](https://bitcoincore.org/en/download/) (0.21+) +and [latest Electrum wallet](https://electrum.org/#download) (4.0+). + +Also, install the following packages (on Debian or Ubuntu): ```bash $ sudo apt update -$ sudo apt install clang cmake # for building 'rust-rocksdb' +$ sudo apt install clang cmake build-essential # for building 'rust-rocksdb' ``` -Note for Raspberry Pi 4 owners: the old versions of OS/toolchains produce broken binaries. Make sure to use latest OS! (see #226) +There are two ways to compile `electrs`: by statically linking to `librocksdb` or dynamically linking. -Optionally, you may install [`cfg_me`](https://github.com/Kixunil/cfg_me) tool for generating the manual page. The easiest way is to run `cargo install cfg_me`. +The advantages of static linking: -### Build +* The binary is self-contained and doesn't need other dependencies, it can be transferred to other machine without worrying +* The binary should work pretty much with every common distro +* Different library installed elsewhere doesn't affect the behavior of `electrs` + +The advantages of dynamic linking: + +* If a (security) bug is found in the library, you only need to upgrade/recompile the library to fix it, no need to recompile `electrs` +* Updating rocksdb can be as simple as `apt upgrade` +* The build is significantly faster (if you already have the binary version of the library from packages) +* The build is deterministic +* Cross compilation is more reliable +* If another application is also using `rocksdb`, you don't store it on disk and in RAM twice + +If you decided to use dynamic linking, you will also need to install the library. +On Debian: + +```bash +$ sudo apt install librocksdb-dev +``` + +#### Preparing for cross compilation + +Cross compilation can save you some time since you can compile `electrs` for a slower computer (like Raspberry Pi) on a faster machine +even with different CPU architecture. +Skip this if it's not your case. + +If you want to cross-compile, you need to install some additional packages. +These cross compilation instructions use `aarch64`/`arm64` + Linux as an example. +(The resulting binary should work on RPi 4 with aarch64-enabled OS). +Change to your desired architecture/OS. + +If you use Debian (or a derived distribution) you need to enable the target architecture: + +``` +$ sudo dpkg --add-architecture arm64 +$ sudo apt update +``` + +If you use `cargo` from the repository + +```bash +$ sudo apt install gcc-aarch64-linux-gnu gcc-aarch64-linux-gnu libc6-dev:arm64 libstd-rust-dev:arm64 +``` + +If you use Rustup: + +```bash +$ sudo apt install gcc-aarch64-linux-gnu gcc-aarch64-linux-gnu libc6-dev:arm64 +$ rustup target add aarch64-unknown-linux-gnu +``` + +If you decided to use the system rocksdb (recommended if the target OS supports it), you need the version from the other architecture: + +```bash +$ sudo apt install librocksdb-dev:arm64 +``` + +#### Preparing man page generation (optional) + +Optionally, you may install [`cfg_me`](https://github.com/Kixunil/cfg_me) tool for generating the manual page. +The easiest way is to run `cargo install cfg_me`. + +#### Download electrs -First build should take ~20 minutes: ```bash $ git clone https://github.com/romanz/electrs $ cd electrs -$ cargo build --release ``` +### Build + +Note: you need to have enough free RAM to build `electrs`. +The build will fail otherwise. +Close those 100 old tabs in the browser. ;) + +#### Static linking + +First build should take ~20 minutes: +```bash +$ cargo build --locked --release +``` + +#### Dynamic linking + +``` +$ ROCKSDB_INCLUDE_DIR=/usr/include ROCKSDB_LIB_DIR=/usr/lib cargo build --locked --no-default-features --release +``` + +(Don't worry about `--no-default-features`, it's only related to rocksdb linking.) + +#### Cross compilation + +Run one of the commands above (depending on linking type) with argument `--target aarch64-unknown-linux-gnu` and prepended with env vars: `BINDGEN_EXTRA_CLANG_ARGS="-target gcc-aarch64-linux-gnu" RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc"` + +E.g. for dynamic linking case: + +``` +$ ROCKSDB_INCLUDE_DIR=/usr/include ROCKSDB_LIB_DIR=/usr/lib BINDGEN_EXTRA_CLANG_ARGS="-target gcc-aarch64-linux-gnu" RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" cargo build --locked --release --target aarch64-unknown-linux-gnu +``` + +It's a bit long but sufficient! You will find the resulting binary in `target/aarch64-unknown-linux-gnu/release/electrs` - copy it to your target machine. + +#### Generating man pages + If you installed `cfg_me` to generate man page, you can run `cfg_me man` to see it right away or `cfg_me -o electrs.1 man` to save it into a file (`electrs.1`). ## Docker-based installation from source +Note: currently Docker installation links statically + +Note: health check only works if Prometheus is running on port 4224 inside container + ```bash $ docker build -t electrs-app . +$ mkdir db $ docker run --network host \ --volume $HOME/.bitcoin:/home/user/.bitcoin:ro \ - --volume $PWD:/home/user \ - --rm -i -t electrs-app \ - electrs -vvvv --timestamp --db-dir /home/user/db + --volume $PWD/db:/home/user/db \ + --env ELECTRS_VERBOSE=4 \ + --env ELECTRS_TIMESTAMP=true \ + --env ELECTRS_DB_DIR=/home/user/db \ + --rm -i -t electrs-app ``` +If not using the host-network, you probably want to expose the ports for electrs and Prometheus like so: + +```bash +$ docker run --volume $HOME/.bitcoin:/home/user/.bitcoin:ro \ + --volume $PWD/db:/home/user/db \ + --env ELECTRS_VERBOSE=4 \ + --env ELECTRS_TIMESTAMP=true \ + --env ELECTRS_DB_DIR=/home/user/db \ + --env ELECTRS_ELECTRUM_RPC_ADDR=0.0.0.0:50001 \ + --env ELECTRS_MONITORING_ADDR=0.0.0.0:4224 \ + --rm -i -t electrs-app +``` + +To access the server from outside Docker, add `-p 50001:50001 -p 4224:4224` but be aware of the security risks. Good practice is to group containers that needs access to the server inside the same Docker network and not expose the ports to the outside world. + ## Native OS packages -There are currently no official/stable binary pckages. +There are currently no official/stable binary packages. -However, there's an [**experimental** repository for Debian 10](https://deb.ln-ask.me) (should work on recent Ubuntu, but not tested well-enough). The repository provides several significant advantages: +However, there's a [*beta* repository for Debian 10](https://deb.ln-ask.me) (should work on recent Ubuntu, but not tested well-enough) +The repository provides several significant advantages: -* Everything is completely automatic - after installing `electrs` via `apt`, it's running and will automatically run on reboot, restart after crash... It also connects to bitcoind out-of-the-box, no messing with config files or anything else. It just works. -* Prebuilt binaries save you a lot of time. The binary installation of all the components is under 3 minutes on common hardware. Building from source is much longer. +* Everything is completely automatic - after installing `electrs` via `apt`, it's running and will automatically run on reboot, restart after crash.. + It also connects to bitcoind out-of-the-box, no messing with config files or anything else. + It just works. +* Prebuilt binaries save you a lot of time. + The binary installation of all the components is under 3 minutes on common hardware. + Building from source is much longer. * The repository contains some seurity hardening out-of-the-box - separate users for services, use of [btc-rpc-proxy](https://github.com/Kixunil/btc-rpc-proxy), etc. -And two significant disadvantages: +And two disadvantages: -* It's currently impossible to independently verify the built packages, so you have to trust the author of the repository. This will hopefully change in the future. -* The repository is considered experimental and not well tested yet. The author of the repository is also a contributor to `electrs` and appreciates [bug reports](https://github.com/Kixunil/cryptoanarchy-deb-repo-builder/issues), [test reports](https://github.com/Kixunil/cryptoanarchy-deb-repo-builder/issues/61), and other contributions. +* It's currently not trivial to independently verify the built packages, so you may need to trust the author of the repository. + The build is now deterministic but nobody verified it independently yet. +* The repository is considered beta. + electrs` seems to work well so far but was not tested heavily. + The author of the repository is also a contributor to `electrs` and appreciates [bug reports](https://github.com/Kixunil/cryptoanarchy-deb-repo-builder/issues), + [test reports](https://github.com/Kixunil/cryptoanarchy-deb-repo-builder/issues/61), and other contributions. ## Manual configuration -This applies only if you do **not** use some other automated systems such as Debian packages. If you use automated systems, refer to their documentation first! +This applies only if you do **not** use some other automated systems such as Debian packages. +If you use automated systems, refer to their documentation first! ### Bitcoind configuration -Pruning must be turned **off** for `electrs` to work. `txindex` is allowed but unnecessary for `electrs`. However, you might still need it if you run other services (e.g.`eclair`) +Pruning must be turned **off** for `electrs` to work. +`txindex` is allowed but unnecessary for `electrs`. +However, you might still need it if you run other services (e.g.`eclair`) -The highly recommended way of authenticating `electrs` is using cookie file. It's the most secure and robust method. Set `rpccookiefile` option of `bitcoind` to a file within an existing directory which it can access. You can skip it if you're running both daemons under the same user and with the default directories. +The highly recommended way of authenticating `electrs` is using cookie file. +It's the most [secure](https://github.com/Kixunil/security_writings/blob/master/cookie_files.md) and robust method. +Set `rpccookiefile` option of `bitcoind` to a file within an existing directory which it can access. +You can skip it if you're running both daemons under the same user and with the default directories. `electrs` will wait for `bitcoind` to sync, however, you will be unabe to use it until the syncing is done. @@ -74,26 +209,52 @@ $ bitcoind -server=1 -txindex=0 -prune=0 ``` ### Electrs configuration -Electrs can be configured using command line, environment variables and configuration files (or their combination). It is highly recommended to use configuration files for any non-trivial setups since it's easier to manage. If you're setting password manually instead of cookie files, configuration file is the only way to set it due to security reasons. +Electrs can be configured using command line, environment variables and configuration files (or their combination). +It is highly recommended to use configuration files for any non-trivial setups since it's easier to manage. +If you're setting password manually instead of cookie files, configuration file is the only way to set it due to security reasons. ### Configuration files and priorities -The config files must be in the Toml format. These config files are (from lowest priority to highest): `/etc/electrs/config.toml`, `~/.electrs/config.toml`, `./electrs.toml`. +The Toml-formatted config files ([an example here](config_example.toml)) are (from lowest priority to highest): `/etc/electrs/config.toml`, `~/.electrs/config.toml`, `./electrs.toml`. -The options in highest-priority config files override options set in lowest-priority config files. Environment variables override options in config files and finally arguments override everythig else. There are two special arguments `--conf` which reads the specified file and `--conf-dir`, which read all the files in the specified directory. The options in those files override **everything that was set previously, including arguments that were passed before these arguments**. In general, later arguments override previous ones. It is a good practice to use these special arguments at the beginning of the command line in order to avoid confusion. +The options in highest-priority config files override options set in lowest-priority config files. -For each command line argument an environment variable of the same name with `ELECTRS_` prefix, upper case letters and underscores instead of hypens exists (e.g. you can use `ELECTRS_ELECTRUM_RPC_ADDR` instead of `--electrum-rpc-addr`). Similarly, for each such argument an option in config file exists with underscores instead of hypens (e.g. `electrum_rpc_addr`). In addition, config files support `cookie` option to specify cookie - this is not available using command line or environment variables for security reasons (other applications could read it otherwise). Note that this is different from using `cookie_path`, which points to a file containing the cookie instead of being the cookie itself. +**Environment variables** override options in config files and finally **arguments** override everythig else. -Finally, you need to use a number in config file if you want to increase verbosity (e.g. `verbose = 3` is equivalent to `-vvv`) and `true` value in case of flags (e.g. `timestamp = true`) +There are two special arguments `--conf` which reads the specified file and `--conf-dir`, which read all the files in the specified directory. -If you are using `-rpcuser=USER` and `-rpcpassword=PASSWORD` of `bitcoind` for authentication, please use `cookie="USER:PASSWORD"` option in one of the [config files](https://github.com/romanz/electrs/blob/master/doc/usage.md#configuration-files-and-priorities). -Otherwise, [`~/.bitcoin/.cookie`](https://github.com/bitcoin/bitcoin/blob/0212187fc624ea4a02fc99bc57ebd413499a9ee1/contrib/debian/examples/bitcoin.conf#L70-L72) will be used as the default cookie file, allowing this server to use bitcoind JSONRPC interface. +The options in those files override **everything** that was set previously, **including arguments** that were passed before these two special arguments. + +In general, later arguments override previous ones. +It is a good practice to use these special arguments at the beginning of the command line in order to avoid confusion. + +**Naming convention** + +For each command line argument an **environment variable** of the same name with `ELECTRS_` prefix, upper case letters and underscores instead of hypens exists +(e.g. you can use `ELECTRS_ELECTRUM_RPC_ADDR` instead of `--electrum-rpc-addr`). + +Similarly, for each such argument an option in config file exists with underscores instead of hypens (e.g. `electrum_rpc_addr`). + +You need to use a number in config file if you want to increase verbosity (e.g. `verbose = 3` is equivalent to `-vvv`) and `true` value in case of flags (e.g. `timestamp = true`) + +**Authentication** + +In addition, config files support `auth` option to specify username and password. +This is not available using command line or environment variables for security reasons (other applications could read it otherwise). +**Important note**: `auth` is different from `cookie_file`, which points to a file containing the cookie instead of being the cookie itself! + +If you are using `-rpcuser=USER` and `-rpcpassword=PASSWORD` of `bitcoind` for authentication, please use `auth="USER:PASSWORD"` option in one of the [config files](https://github.com/romanz/electrs/blob/master/doc/usage.md#configuration-files-and-priorities). +Otherwise, [`~/.bitcoin/.cookie`](https://github.com/bitcoin/bitcoin/blob/0212187fc624ea4a02fc99bc57ebd413499a9ee1/contrib/debian/examples/bitcoin.conf#L70-L72) will be used as the default cookie file, +allowing this server to use bitcoind JSONRPC interface. + +Note: there was a `cookie` option in the version 0.8.7 and below, it's now deprecated - do **not** use, it will be removed. +Please read upgrade notes if you're upgrading to a newer version. ### Electrs usage First index sync should take ~1.5 hours (on a dual core Intel CPU @ 3.3 GHz, 8 GB RAM, 1TB WD Blue HDD): ```bash -$ cargo run --release -- -vvv --timestamp --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" +$ ./target/release/electrs -vvv --timestamp --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" 2018-08-17T18:27:42 - INFO - NetworkInfo { version: 179900, subversion: "/Satoshi:0.17.99/" } 2018-08-17T18:27:42 - INFO - BlockchainInfo { chain: "main", blocks: 537204, headers: 537204, bestblockhash: "0000000000000000002956768ca9421a8ddf4e53b1d81e429bd0125a383e3636", pruned: false, initialblockdownload: false } 2018-08-17T18:27:42 - DEBUG - opening DB at "./db/mainnet" @@ -116,14 +277,16 @@ $ cargo run --release -- -vvv --timestamp --db-dir ./db --electrum-rpc-addr="127 2018-08-17T19:58:28 - DEBUG - applying 14 new headers from height 537205 2018-08-17T19:58:29 - INFO - RPC server running on 127.0.0.1:50001 ``` -You can specify options via command-line parameters, environment variables or using config files. See the documentation above. +You can specify options via command-line parameters, environment variables or using config files. +See the documentation above. Note that the final DB size should be ~20% of the `blk*.dat` files, but it may increase to ~35% at the end of the inital sync (just before the [full compaction is invoked](https://github.com/facebook/rocksdb/wiki/Manual-Compaction)). -If initial sync fails due to `memory allocation of xxxxxxxx bytes failedAborted` errors, as may happen on devices with limited RAM, try the following arguments when starting `electrs`. It should take roughly 18 hours to sync and compact the index on an ODROID-HC1 with 8 CPU cores @ 2GHz, 2GB RAM, and an SSD using the following command: +If initial sync fails due to `memory allocation of xxxxxxxx bytes failedAborted` errors, as may happen on devices with limited RAM, try the following arguments when starting `electrs`. +It should take roughly 18 hours to sync and compact the index on an ODROID-HC1 with 8 CPU cores @ 2GHz, 2GB RAM, and an SSD using the following command: ```bash -$ cargo run --release -- -vvvv --index-batch-size=10 --jsonrpc-import --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" +$ ./target/release/electrs -vvvv --index-batch-size=10 --jsonrpc-import --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" ``` The index database is stored here: @@ -136,7 +299,8 @@ See below for [extra configuration suggestions](https://github.com/romanz/electr ## Electrum client -If you happen to use the Electrum client from [the **experimental** Debian repository](https://github.com/romanz/electrs/blob/master/doc/usage.md#cnative-os-packages), it's pre-configured out-of-the-box already. Read below otherwise. +If you happen to use the Electrum client from [the *beta* Debian repository](https://github.com/romanz/electrs/blob/master/doc/usage.md#cnative-os-packages), it's pre-configured out-of-the-box already +Read below otherwise. There's a prepared script for launching `electrum` in such way to connect only to the local `electrs` instance to protect your privacy. @@ -160,7 +324,8 @@ $ electrum # will connect only to the local server ### SSL connection -In order to use a secure connection, you can also use [NGINX as an SSL endpoint](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/#) by placing the following block in `nginx.conf`. +In order to use a secure connection, you can also use [NGINX as an SSL endpoint](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/#) +by placing the following block in `nginx.conf`. ```nginx stream { @@ -211,7 +376,8 @@ HiddenServiceVersion 3 HiddenServicePort 50001 127.0.0.1:50001 ``` -If you use [the **experimental** Debian repository](https://github.com/romanz/electrs/blob/master/doc/usage.md#cnative-os-packages), it is cleaner to install `tor-hs-patch-config` using `apt` and then placing the configuration into a file inside `/etc/tor/hidden-services.d`. +If you use [the *beta* Debian repository](https://github.com/romanz/electrs/blob/master/doc/usage.md#cnative-os-packages), +it is cleaner to install `tor-hs-patch-config` using `apt` and then placing the configuration into a file inside `/etc/tor/hidden-services.d`. Restart the service: ``` @@ -233,9 +399,11 @@ For more details, see http://docs.electrum.org/en/latest/tor.html. ### Sample Systemd Unit File -If you use [the **experimental** Debian repository](https://github.com/romanz/electrs/blob/master/doc/usage.md#cnative-os-packages), you should skip this section, as the appropriate systemd unit file is installed automatically. +If you use [the *beta* Debian repository](https://github.com/romanz/electrs/blob/master/doc/usage.md#cnative-os-packages), you should skip this section, +as the appropriate systemd unit file is installed automatically. -You may wish to have systemd manage electrs so that it's "always on." Here is a sample unit file (which assumes that the bitcoind unit file is `bitcoind.service`): +You may wish to have systemd manage electrs so that it's "always on". +Here is a sample unit file (which assumes that the bitcoind unit file is `bitcoind.service`): ``` [Unit] @@ -278,8 +446,8 @@ $ firefox 'http://localhost:9090/graph?g0.range_input=1h&g0.expr=index_height&g0 You can invoke any supported RPC using `netcat`, for example: ``` -$ echo '{"jsonrpc": "2.0", "method": "server.version", "id": 0}' | netcat 127.0.0.1 50001 -{"id":0,"jsonrpc":"2.0","result":["electrs 0.8.5","1.4"]} +$ echo '{"jsonrpc": "2.0", "method": "server.version", "params": ["", "1.4"], "id": 0}' | netcat 127.0.0.1 50001 +{"id":0,"jsonrpc":"2.0","result":["electrs 0.8.6","1.4"]} ``` For more complex tasks, you may need to convert addresses to @@ -290,3 +458,50 @@ For more complex tasks, you may need to convert addresses to $ ./contrib/addr.py 144STc7gcb9XCp6t4hvrcUEKg9KemivsCR # sample address from block #640699 144STc7gcb9XCp6t4hvrcUEKg9KemivsCR has {'confirmed': 12652436, 'unconfirmed': 0} satoshis ``` + +## Upgrading + +> **If you're upgrading from version 0.8.7 to a higher version and used `cookie` option you should change your configuration!** +> The `cookie` option was deprecated and **will be removed eventually**! +> If you had actual cookie (from `~/bitcoin/.cookie` file) specified in `cookie` option, this was wrong as it wouldn't get updated when needed. +> It's strongly recommended to use proper cookie authentication using `cookie_file`. +> If you really have to use fixed username and password, explicitly specified in `bitcoind` config, use `auth` option instead. +> Users of `btc-rpc-proxy` using `public:public` need to use `auth` too. +> You can read [a detailed explanation of cookie deprecation with motivation explained](cookie_deprecation.md). + +As with any other application, you need to remember how you installed `electrs` to upgrade it. +If you don't then here's a little help: run `which electrs` and compare the output + +* If you got an error you didn't install `electrs` into your system in any way, it's probably sitting in the `target/release` directory of source +* If the path starts with `/bin/` then either you have used packaging system or you made a mistake the first time (non-packaged binaries must go to `/usr/local/bin`) +* If the path starts with `/usr/local/bin` you most likely copied electrs there after building +* If the path starts with `/home/YOUR_USERNAME/.cargo/bin` you most likely ran `cargo install` + +### Upgrading distribution package + +If you used Debian packaging system you only need this: + +``` +sudo apt update +sudo apt upgrade +``` + +Similarly for other distributions - use their respective commands. +If a new version of `electrs` is not yet in the package system, try wait a few days or contact the maintainers of the packages if it's been a long time. + +### Upgrading manual installation + +1. Enter your `electrs` source directory, usually in `~/` but some people like to put it in something like `~/sources`. + If you've deleted it, you need to `git clone` again. +2. `git checkout master` +3. `git pull` +4. Strongly recommended: `git verify-tag v0.8.6` (fix the version number if we've forgotten to update this docs ;)) should show "Good signature from 15C8 C357 4AE4 F1E2 5F3F 35C5 87CA E5FA 4691 7CBB" +5. `git checkout v0.8.6` +6. If you used static linking: `cargo build --locked --release`. + If you used dynamic linking `ROCKSDB_INCLUDE_DIR=/usr/include ROCKSDB_LIB_DIR=/usr/lib cargo build --locked --no-default-features --release`. + If you don't remember which linking you used, you probably used static. + This step will take a few tens of minutes (but dynamic linking is a bit faster), go grab a coffee. + Also remember that you need enough free RAM, the build will die otherwise +7. If you've previously copied `electrs` into `/usr/local/bin` run: sudo `cp target/release/electrs /usr/local/bin` + If you've previously installed `electrs` using `cargo install`: `cargo install --locked --path . -f` +8. If you've manually configured systemd service: `sudo systemctl restart electrs` diff --git a/examples/compact.rs b/examples/compact.rs deleted file mode 100644 index 4c373ff..0000000 --- a/examples/compact.rs +++ /dev/null @@ -1,29 +0,0 @@ -/// Benchmark full compaction. -extern crate electrs; - -#[macro_use] -extern crate log; - -extern crate error_chain; - -use electrs::{config::Config, errors::*, store::DBStore}; - -use error_chain::ChainedError; - -fn run(config: Config) -> Result<()> { - if !config.db_path.exists() { - panic!( - "DB {:?} must exist when running this benchmark!", - config.db_path - ); - } - let store = DBStore::open(&config.db_path, /*low_memory=*/ true); - store.compact(); - Ok(()) -} - -fn main() { - if let Err(e) = run(Config::from_args()) { - error!("{}", e.display_chain()); - } -} diff --git a/examples/index.rs b/examples/index.rs deleted file mode 100644 index ab68fc6..0000000 --- a/examples/index.rs +++ /dev/null @@ -1,42 +0,0 @@ -/// Benchmark regular indexing flow (using JSONRPC), don't persist the resulting index. -extern crate electrs; -extern crate error_chain; - -#[macro_use] -extern crate log; - -use electrs::{ - cache::BlockTxIDsCache, config::Config, daemon::Daemon, errors::*, fake::FakeStore, - index::Index, metrics::Metrics, signal::Waiter, -}; -use error_chain::ChainedError; -use std::sync::Arc; - -fn run() -> Result<()> { - let signal = Waiter::start(); - let config = Config::from_args(); - let metrics = Metrics::new(config.monitoring_addr); - metrics.start(); - let cache = Arc::new(BlockTxIDsCache::new(0, &metrics)); - - let daemon = Daemon::new( - &config.daemon_dir, - &config.blocks_dir, - config.daemon_rpc_addr, - config.cookie_getter(), - config.network_type, - signal.clone(), - cache, - &metrics, - )?; - let fake_store = FakeStore {}; - let index = Index::load(&fake_store, &daemon, &metrics, config.index_batch_size)?; - index.update(&fake_store, &signal)?; - Ok(()) -} - -fn main() { - if let Err(e) = run() { - error!("{}", e.display_chain()); - } -} diff --git a/examples/txid_collisions.rs b/examples/txid_collisions.rs deleted file mode 100644 index 8694de3..0000000 --- a/examples/txid_collisions.rs +++ /dev/null @@ -1,49 +0,0 @@ -extern crate electrs; - -extern crate hex; -extern crate log; - -use electrs::{config::Config, store::DBStore}; - -fn max_collision(store: DBStore, prefix: &[u8]) { - let prefix_len = prefix.len(); - let mut prev: Option> = None; - let mut collision_max = 0; - - for row in store.iter_scan(prefix) { - assert!(row.key.starts_with(prefix)); - if let Some(prev) = prev { - let collision_len = prev - .iter() - .zip(row.key.iter()) - .take_while(|(a, b)| a == b) - .count(); - if collision_len > collision_max { - eprintln!( - "{} bytes collision found:\n{:?}\n{:?}\n", - collision_len - prefix_len, - revhex(&prev[prefix_len..]), - revhex(&row.key[prefix_len..]), - ); - collision_max = collision_len; - } - } - prev = Some(row.key.to_vec()); - } -} - -fn revhex(value: &[u8]) -> String { - hex::encode(&value.iter().cloned().rev().collect::>()) -} - -fn run(config: Config) { - if !config.db_path.exists() { - panic!("DB {:?} must exist when running this tool!", config.db_path); - } - let store = DBStore::open(&config.db_path, /*low_memory=*/ false); - max_collision(store, b"T"); -} - -fn main() { - run(Config::from_args()); -} diff --git a/internal/README.md b/internal/README.md new file mode 100644 index 0000000..1a1e651 --- /dev/null +++ b/internal/README.md @@ -0,0 +1,3 @@ +# electrs-internal files + +**Nothing for users here, just for developers. ;)** diff --git a/config_spec.toml b/internal/config_specification.toml similarity index 77% rename from config_spec.toml rename to internal/config_specification.toml index b061ffd..c8a7e99 100644 --- a/config_spec.toml +++ b/internal/config_specification.toml @@ -37,7 +37,15 @@ doc = "Analogous to bitcoind's -blocksdir option, this specifies the directory c [[param]] name = "cookie" type = "String" -doc = "JSONRPC authentication cookie ('USER:PASSWORD', default: read from cookie file)" +doc = "DEPRECATED: use cookie_file or auth instead!" +# Force the user to use config file in order to avoid password leaks +argument = false +env_var = false + +[[param]] +name = "auth" +type = "String" +doc = "JSONRPC authentication ('USER:PASSWORD', default: use cookie file)" # Force the user to use config file in order to avoid password leaks argument = false env_var = false @@ -52,23 +60,27 @@ doc = "JSONRPC authentication cookie file (default: ~/.bitcoin/.cookie)" name = "network" type = "crate::config::BitcoinNetwork" convert_into = "::bitcoin::network::constants::Network" -doc = "Select Bitcoin network type ('bitcoin', 'testnet' or 'regtest')" +doc = "Select Bitcoin network type ('bitcoin', 'testnet', 'regtest' or 'signet')" default = "Default::default()" [[param]] name = "electrum_rpc_addr" type = "crate::config::ResolvAddr" -doc = "Electrum server JSONRPC 'addr:port' to listen on (default: '127.0.0.1:50001' for mainnet, '127.0.0.1:60001' for testnet and '127.0.0.1:60401' for regtest)" +doc = "Electrum server JSONRPC 'addr:port' to listen on (default: '127.0.0.1:50001' for mainnet, '127.0.0.1:60001' for testnet, '127.0.0.1:60401' for regtest and '127.0.0.1:60601' for signet)" [[param]] name = "daemon_rpc_addr" type = "crate::config::ResolvAddr" -doc = "Bitcoin daemon JSONRPC 'addr:port' to connect (default: 127.0.0.1:8332 for mainnet, 127.0.0.1:18332 for testnet and 127.0.0.1:18443 for regtest)" +doc = "Bitcoin daemon JSONRPC 'addr:port' to connect (default: 127.0.0.1:8332 for mainnet, 127.0.0.1:18332 for testnet, 127.0.0.1:18443 for regtest and 127.0.0.1:18554 for signet)" +[[param]] +name = "daemon_p2p_addr" +type = "crate::config::ResolvAddr" +doc = "Bitcoin daemon p2p 'addr:port' to connect (default: 127.0.0.1:8333 for mainnet, 127.0.0.1:18333 for testnet, 127.0.0.1:18444 for regtest and 127.0.0.1:38333 for signet)" [[param]] name = "monitoring_addr" type = "crate::config::ResolvAddr" -doc = "Prometheus monitoring 'addr:port' to listen on (default: 127.0.0.1:4224 for mainnet, 127.0.0.1:14224 for testnet and 127.0.0.1:24224 for regtest)" +doc = "Prometheus monitoring 'addr:port' to listen on (default: 127.0.0.1:4224 for mainnet, 127.0.0.1:14224 for testnet, 127.0.0.1:24224 for regtest and 127.0.0.1:34224 for regtest)" [[switch]] name = "jsonrpc_import" @@ -78,19 +90,13 @@ doc = "Use JSONRPC instead of directly importing blk*.dat files. Useful for remo name = "wait_duration_secs" type = "u64" doc = "Duration to wait between bitcoind polling" -default = "5" +default = "10" [[param]] name = "index_batch_size" type = "usize" doc = "Number of blocks to get in one JSONRPC request from bitcoind" -default = "100" - -[[param]] -name = "bulk_index_threads" -type = "usize" -doc = "Number of threads used for bulk indexing (default: use the # of CPUs)" -default = "0" +default = "10" [[param]] name = "tx_cache_size_mb" diff --git a/query.sh b/query.sh new file mode 100755 index 0000000..26054bd --- /dev/null +++ b/query.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -eux +cd `dirname $0` + +cargo fmt --all +cargo build --all --release + +NETWORK=$1 +shift + +CMD="target/release/query --network $NETWORK --db-dir ./db2 --daemon-dir $HOME/.bitcoin" +export RUST_LOG=${RUST_LOG-info} +$CMD $* + +# use SIGINT to quit diff --git a/server.sh b/server.sh new file mode 100755 index 0000000..39ed75f --- /dev/null +++ b/server.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -eux +cd `dirname $0` + +cargo fmt --all +cargo build --all --release + +NETWORK=$1 +shift + +DB=./db2 # $HOME/tmp/electrs_db/mainnet_zstd +CMD="target/release/electrs --network $NETWORK --db-dir $DB --daemon-dir $HOME/.bitcoin" +export RUST_LOG=${RUST_LOG-info} +$CMD $* + +# use SIGINT to quit diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index 0e4f275..0000000 --- a/src/app.rs +++ /dev/null @@ -1,60 +0,0 @@ -use bitcoin::hash_types::BlockHash; -use std::sync::{Arc, Mutex}; - -use crate::{config::Config, daemon, errors::*, index, signal::Waiter, store}; - -pub struct App { - store: store::DBStore, - index: index::Index, - daemon: daemon::Daemon, - banner: String, - tip: Mutex, -} - -impl App { - pub fn new( - store: store::DBStore, - index: index::Index, - daemon: daemon::Daemon, - config: &Config, - ) -> Result> { - Ok(Arc::new(App { - store, - index, - daemon: daemon.reconnect()?, - banner: config.server_banner.clone(), - tip: Mutex::new(BlockHash::default()), - })) - } - - fn write_store(&self) -> &impl store::WriteStore { - &self.store - } - // TODO: use index for queries. - pub fn read_store(&self) -> &dyn store::ReadStore { - &self.store - } - pub fn index(&self) -> &index::Index { - &self.index - } - pub fn daemon(&self) -> &daemon::Daemon { - &self.daemon - } - - pub fn update(&self, signal: &Waiter) -> Result { - let mut tip = self.tip.lock().expect("failed to lock tip"); - let new_block = *tip != self.daemon().getbestblockhash()?; - if new_block { - *tip = self.index().update(self.write_store(), &signal)?; - } - Ok(new_block) - } - - pub fn get_banner(&self) -> Result { - Ok(format!( - "{}\n{}", - self.banner, - self.daemon.get_subversion()? - )) - } -} diff --git a/src/bin/electrs.rs b/src/bin/electrs.rs index 12cef4e..0095ffc 100644 --- a/src/bin/electrs.rs +++ b/src/bin/electrs.rs @@ -1,89 +1,15 @@ -extern crate electrs; +#![recursion_limit = "256"] -extern crate error_chain; -#[macro_use] -extern crate log; +use anyhow::{Context, Result}; +use electrs::{server, Config, Daemon, Rpc, Tracker}; -use error_chain::ChainedError; -use std::process; -use std::sync::Arc; - -use electrs::{ - app::App, - bulk, - cache::{BlockTxIDsCache, TransactionCache}, - config::Config, - daemon::Daemon, - errors::*, - index::Index, - metrics::Metrics, - query::Query, - rpc::RPC, - signal::Waiter, - store::{full_compaction, is_fully_compacted, DBStore}, -}; - -fn run_server(config: &Config) -> Result<()> { - let signal = Waiter::start(); - let metrics = Metrics::new(config.monitoring_addr); - metrics.start(); - let blocktxids_cache = Arc::new(BlockTxIDsCache::new(config.blocktxids_cache_size, &metrics)); - - let daemon = Daemon::new( - &config.daemon_dir, - &config.blocks_dir, - config.daemon_rpc_addr, - config.cookie_getter(), - config.network_type, - signal.clone(), - blocktxids_cache, - &metrics, - )?; - // Perform initial indexing from local blk*.dat block files. - let store = DBStore::open(&config.db_path, /*low_memory=*/ config.jsonrpc_import); - let index = Index::load(&store, &daemon, &metrics, config.index_batch_size)?; - let store = if is_fully_compacted(&store) { - store // initial import and full compaction are over - } else if config.jsonrpc_import { - index.update(&store, &signal)?; // slower: uses JSONRPC for fetching blocks - full_compaction(store) - } else { - // faster, but uses more memory - let store = - bulk::index_blk_files(&daemon, config.bulk_index_threads, &metrics, &signal, store)?; - let store = full_compaction(store); - index.reload(&store); // make sure the block header index is up-to-date - store - } - .enable_compaction(); // enable auto compactions before starting incremental index updates. - - let app = App::new(store, index, daemon, &config)?; - let tx_cache = TransactionCache::new(config.tx_cache_size, &metrics); - let query = Query::new(app.clone(), &metrics, tx_cache, config.txid_limit); - let relayfee = query.get_relayfee()?; - debug!("relayfee: {} BTC", relayfee); - - let mut server = None; // Electrum RPC server - loop { - app.update(&signal)?; - query.update_mempool()?; - server - .get_or_insert_with(|| { - RPC::start(config.electrum_rpc_addr, query.clone(), &metrics, relayfee) - }) - .notify(); // update subscribed clients - if let Err(err) = signal.wait(config.wait_duration) { - info!("stopping server: {}", err); - break; - } - } - Ok(()) -} - -fn main() { +fn main() -> Result<()> { let config = Config::from_args(); - if let Err(e) = run_server(&config) { - error!("server failed: {}", e.display_chain()); - process::exit(1); - } + let mut tracker = Tracker::new(&config)?; + tracker + .sync(&Daemon::connect(&config)?) + .context("initial sync failed")?; + + // re-connect after initial sync (due to possible timeout during compaction) + server::run(&config, Rpc::new(&config, tracker)?).context("server failed") } diff --git a/src/bin/query.rs b/src/bin/query.rs new file mode 100644 index 0000000..c1f73a0 --- /dev/null +++ b/src/bin/query.rs @@ -0,0 +1,43 @@ +#[macro_use] +extern crate log; + +use anyhow::Result; +use bitcoin::{Address, Amount}; + +use std::collections::BTreeMap; +use std::str::FromStr; + +use electrs::{Cache, Config, Daemon, ScriptHash, Status, Tracker}; + +fn main() -> Result<()> { + let config = Config::from_args(); + let addresses = config + .args + .iter() + .map(|a| Address::from_str(a).expect("invalid address")); + + let cache = Cache::new(); + let daemon = Daemon::connect(&config)?; + let mut tracker = Tracker::new(&config)?; + let mut map: BTreeMap = addresses + .map(|addr| { + let status = Status::new(ScriptHash::new(&addr.script_pubkey())); + (addr, status) + }) + .collect(); + + loop { + tracker.sync(&daemon)?; + let mut total = Amount::ZERO; + for (addr, status) in map.iter_mut() { + tracker.update_status(status, &daemon, &cache)?; + let balance = tracker.get_balance(status, &cache); + if balance > Amount::ZERO { + info!("{} has {}", addr, balance); + } + total += balance; + } + info!("total: {}", total); + std::thread::sleep(config.wait_duration); + } +} diff --git a/src/bin/sync.rs b/src/bin/sync.rs new file mode 100644 index 0000000..ef91958 --- /dev/null +++ b/src/bin/sync.rs @@ -0,0 +1,9 @@ +use anyhow::Result; + +use electrs::{Config, Daemon, Tracker}; + +fn main() -> Result<()> { + let config = Config::from_args(); + let daemon = Daemon::connect(&config)?; + Tracker::new(&config)?.sync(&daemon) +} diff --git a/src/bulk.rs b/src/bulk.rs deleted file mode 100644 index d511e7c..0000000 --- a/src/bulk.rs +++ /dev/null @@ -1,289 +0,0 @@ -use bitcoin::blockdata::block::Block; -use bitcoin::consensus::encode::{deserialize, Decodable}; -use bitcoin::hash_types::BlockHash; -use std::collections::HashSet; -use std::fs; -use std::io::Cursor; -use std::path::{Path, PathBuf}; -use std::sync::{ - mpsc::{Receiver, SyncSender}, - Arc, Mutex, -}; -use std::thread; - -use crate::daemon::Daemon; -use crate::errors::*; -use crate::index::{index_block, last_indexed_block, read_indexed_blockhashes}; -use crate::metrics::{CounterVec, Histogram, HistogramOpts, HistogramVec, MetricOpts, Metrics}; -use crate::signal::Waiter; -use crate::store::{DBStore, Row, WriteStore}; -use crate::util::{spawn_thread, HeaderList, SyncChannel}; - -struct Parser { - magic: u32, - current_headers: HeaderList, - indexed_blockhashes: Mutex>, - // metrics - duration: HistogramVec, - block_count: CounterVec, - bytes_read: Histogram, -} - -impl Parser { - fn new( - daemon: &Daemon, - metrics: &Metrics, - indexed_blockhashes: HashSet, - ) -> Result> { - Ok(Arc::new(Parser { - magic: daemon.magic(), - current_headers: load_headers(daemon)?, - indexed_blockhashes: Mutex::new(indexed_blockhashes), - duration: metrics.histogram_vec( - HistogramOpts::new( - "electrs_parse_duration", - "blk*.dat parsing duration (in seconds)", - ), - &["step"], - ), - block_count: metrics.counter_vec( - MetricOpts::new("electrs_parse_blocks", "# of block parsed (from blk*.dat)"), - &["type"], - ), - - bytes_read: metrics.histogram(HistogramOpts::new( - "electrs_parse_bytes_read", - "# of bytes read (from blk*.dat)", - )), - })) - } - - fn last_indexed_row(&self) -> Row { - // TODO: use JSONRPC for missing blocks, and don't use 'L' row at all. - let indexed_blockhashes = self.indexed_blockhashes.lock().unwrap(); - let last_header = self - .current_headers - .iter() - .take_while(|h| indexed_blockhashes.contains(h.hash())) - .last() - .expect("no indexed header found"); - debug!("last indexed block: {:?}", last_header); - last_indexed_block(last_header.hash()) - } - - fn read_blkfile(&self, path: &Path) -> Result> { - let timer = self.duration.with_label_values(&["read"]).start_timer(); - let blob = fs::read(&path).chain_err(|| format!("failed to read {:?}", path))?; - timer.observe_duration(); - self.bytes_read.observe(blob.len() as f64); - Ok(blob) - } - - fn index_blkfile(&self, blob: Vec) -> Result> { - let timer = self.duration.with_label_values(&["parse"]).start_timer(); - let blocks = parse_blocks(blob, self.magic)?; - timer.observe_duration(); - - let mut rows = Vec::::new(); - let timer = self.duration.with_label_values(&["index"]).start_timer(); - for block in blocks { - let blockhash = block.block_hash(); - if let Some(header) = self.current_headers.header_by_blockhash(&blockhash) { - if self - .indexed_blockhashes - .lock() - .expect("indexed_blockhashes") - .insert(blockhash) - { - rows.extend(index_block(&block, header.height())); - self.block_count.with_label_values(&["indexed"]).inc(); - } else { - self.block_count.with_label_values(&["duplicate"]).inc(); - } - } else { - // will be indexed later (after bulk load is over) if not an orphan block - self.block_count.with_label_values(&["skipped"]).inc(); - } - } - timer.observe_duration(); - - let timer = self.duration.with_label_values(&["sort"]).start_timer(); - rows.sort_unstable_by(|a, b| a.key.cmp(&b.key)); - timer.observe_duration(); - Ok(rows) - } -} - -fn parse_blocks(blob: Vec, magic: u32) -> Result> { - let mut cursor = Cursor::new(&blob); - let mut blocks = vec![]; - let max_pos = blob.len() as u64; - while cursor.position() < max_pos { - let offset = cursor.position(); - match u32::consensus_decode(&mut cursor) { - Ok(value) => { - if magic != value { - cursor.set_position(offset + 1); - continue; - } - } - Err(_) => break, // EOF - }; - let block_size = u32::consensus_decode(&mut cursor).chain_err(|| "no block size")?; - let start = cursor.position(); - let end = start + block_size as u64; - - // If Core's WriteBlockToDisk ftell fails, only the magic bytes and size will be written - // and the block body won't be written to the blk*.dat file. - // Since the first 4 bytes should contain the block's version, we can skip such blocks - // by peeking the cursor (and skipping previous `magic` and `block_size`). - match u32::consensus_decode(&mut cursor) { - Ok(value) => { - if magic == value { - cursor.set_position(start); - continue; - } - } - Err(_) => break, // EOF - } - let block: Block = deserialize(&blob[start as usize..end as usize]) - .chain_err(|| format!("failed to parse block at {}..{}", start, end))?; - blocks.push(block); - cursor.set_position(end as u64); - } - Ok(blocks) -} - -fn load_headers(daemon: &Daemon) -> Result { - let tip = daemon.getbestblockhash()?; - let mut headers = HeaderList::empty(); - let new_headers = headers.order(daemon.get_new_headers(&headers, &tip)?); - headers.apply(new_headers, tip); - Ok(headers) -} - -fn set_open_files_limit(limit: libc::rlim_t) { - let resource = libc::RLIMIT_NOFILE; - let mut rlim = libc::rlimit { - rlim_cur: 0, - rlim_max: 0, - }; - let result = unsafe { libc::getrlimit(resource, &mut rlim) }; - if result < 0 { - panic!("getrlimit() failed: {}", result); - } - rlim.rlim_cur = limit; // set softs limit only. - let result = unsafe { libc::setrlimit(resource, &rlim) }; - if result < 0 { - panic!("setrlimit() failed: {}", result); - } -} - -type JoinHandle = thread::JoinHandle>; -type BlobReceiver = Arc, PathBuf)>>>; - -fn start_reader(blk_files: Vec, parser: Arc) -> (BlobReceiver, JoinHandle) { - let chan = SyncChannel::new(0); - let blobs = chan.sender(); - let handle = spawn_thread("bulk_read", move || -> Result<()> { - for path in blk_files { - blobs - .send((parser.read_blkfile(&path)?, path)) - .expect("failed to send blk*.dat contents"); - } - Ok(()) - }); - (Arc::new(Mutex::new(chan.into_receiver())), handle) -} - -fn start_indexer( - blobs: BlobReceiver, - parser: Arc, - writer: SyncSender<(Vec, PathBuf)>, -) -> JoinHandle { - spawn_thread("bulk_index", move || -> Result<()> { - loop { - let msg = blobs.lock().unwrap().recv(); - if let Ok((blob, path)) = msg { - let rows = parser - .index_blkfile(blob) - .chain_err(|| format!("failed to index {:?}", path))?; - writer - .send((rows, path)) - .expect("failed to send indexed rows") - } else { - debug!("no more blocks to index"); - break; - } - } - Ok(()) - }) -} - -pub fn index_blk_files( - daemon: &Daemon, - index_threads: usize, - metrics: &Metrics, - signal: &Waiter, - store: DBStore, -) -> Result { - set_open_files_limit(2048); // twice the default `ulimit -n` value - let blk_files = daemon.list_blk_files()?; - info!("indexing {} blk*.dat files", blk_files.len()); - let indexed_blockhashes = read_indexed_blockhashes(&store); - debug!("found {} indexed blocks", indexed_blockhashes.len()); - let parser = Parser::new(daemon, metrics, indexed_blockhashes)?; - let (blobs, reader) = start_reader(blk_files, parser.clone()); - let rows_chan = SyncChannel::new(0); - let indexers: Vec = (0..index_threads) - .map(|_| start_indexer(blobs.clone(), parser.clone(), rows_chan.sender())) - .collect(); - - for (rows, path) in rows_chan.into_receiver() { - trace!("indexed {:?}: {} rows", path, rows.len()); - store.write(rows); - signal - .poll() - .chain_err(|| "stopping bulk indexing due to signal")?; - } - reader - .join() - .expect("reader panicked") - .expect("reader failed"); - - indexers.into_iter().for_each(|i| { - i.join() - .expect("indexer panicked") - .expect("indexing failed") - }); - store.write(vec![parser.last_indexed_row()]); - Ok(store) -} - -#[cfg(test)] -mod tests { - - use super::*; - use bitcoin::hashes::Hash; - use hex::decode as hex_decode; - - #[test] - fn test_incomplete_block_parsing() { - let magic = 0x0709110b; - let raw_blocks = hex_decode(fixture("incomplete_block.hex")).unwrap(); - let blocks = parse_blocks(raw_blocks, magic).unwrap(); - assert_eq!(blocks.len(), 2); - assert_eq!( - blocks[1].block_hash().into_inner().to_vec(), - hex_decode("d55acd552414cc44a761e8d6b64a4d555975e208397281d115336fc500000000").unwrap() - ); - } - - pub fn fixture(filename: &str) -> String { - let path = Path::new("src") - .join("tests") - .join("fixtures") - .join(filename); - fs::read_to_string(path).unwrap() - } -} diff --git a/src/cache.rs b/src/cache.rs index 7c7e13c..ec9de39 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,273 +1,48 @@ -use crate::errors::*; -use crate::metrics::{CounterVec, MetricOpts, Metrics}; +use bitcoin::{BlockHash, Transaction, Txid}; -use bitcoin::blockdata::transaction::Transaction; -use bitcoin::consensus::encode::deserialize; -use bitcoin::hash_types::{BlockHash, Txid}; -use lru::LruCache; -use prometheus::IntGauge; -use std::hash::Hash; -use std::sync::Mutex; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; -struct SizedLruCache { - map: LruCache, - bytes_usage: usize, - bytes_capacity: usize, - lookups: CounterVec, - usage: IntGauge, +use crate::merkle::Proof; + +pub struct Cache { + txs: Arc>>, + proofs: Arc>>, } -impl SizedLruCache { - fn new(bytes_capacity: usize, lookups: CounterVec, usage: IntGauge) -> SizedLruCache { - SizedLruCache { - map: LruCache::unbounded(), - bytes_usage: 0, - bytes_capacity, - lookups, - usage, - } +impl Cache { + pub fn new() -> Self { + let txs = Arc::new(RwLock::new(HashMap::new())); + let proofs = Arc::new(RwLock::new(HashMap::new())); + Self { txs, proofs } } - fn get(&mut self, key: &K) -> Option<&V> { - match self.map.get(key) { - None => { - self.lookups.with_label_values(&["miss"]).inc(); - None - } - Some((value, _)) => { - self.lookups.with_label_values(&["hit"]).inc(); - Some(value) - } - } + pub(crate) fn add_tx(&self, txid: Txid, f: impl FnOnce() -> Transaction) { + self.txs.write().unwrap().entry(txid).or_insert_with(f); } - fn put(&mut self, key: K, value: V, byte_size: usize) { - if byte_size > self.bytes_capacity { - return; - } - if let Some((_, popped_size)) = self.map.put(key, (value, byte_size)) { - self.bytes_usage -= popped_size - } - self.bytes_usage += byte_size; - - while self.bytes_usage > self.bytes_capacity { - match self.map.pop_lru() { - Some((_, (_, popped_size))) => self.bytes_usage -= popped_size, - None => break, - } - } - - self.usage.set(self.bytes_usage as i64); - } -} - -pub struct BlockTxIDsCache { - map: Mutex>>, -} - -impl BlockTxIDsCache { - pub fn new(bytes_capacity: usize, metrics: &Metrics) -> BlockTxIDsCache { - let lookups = metrics.counter_vec( - MetricOpts::new( - "electrs_blocktxids_cache", - "# of cache lookups for list of transactions in a block", - ), - &["type"], - ); - let usage = metrics.gauge_int(MetricOpts::new( - "electrs_blocktxids_cache_size", - "Cache usage for list of transactions in a block (bytes)", - )); - BlockTxIDsCache { - map: Mutex::new(SizedLruCache::new(bytes_capacity, lookups, usage)), - } - } - - pub fn get_or_else(&self, blockhash: &BlockHash, load_txids_func: F) -> Result> + pub(crate) fn get_tx(&self, txid: &Txid, f: F) -> Option where - F: FnOnce() -> Result>, + F: FnOnce(&Transaction) -> T, { - if let Some(txids) = self.map.lock().unwrap().get(blockhash) { - return Ok(txids.clone()); - } - - let txids = load_txids_func()?; - let byte_size = 32 /* hash size */ * (1 /* key */ + txids.len() /* values */); - self.map - .lock() - .unwrap() - .put(*blockhash, txids.clone(), byte_size); - Ok(txids) - } -} - -pub struct TransactionCache { - // Store serialized transaction (should use less RAM). - map: Mutex>>, -} - -impl TransactionCache { - pub fn new(bytes_capacity: usize, metrics: &Metrics) -> TransactionCache { - let lookups = metrics.counter_vec( - MetricOpts::new( - "electrs_transactions_cache", - "# of cache lookups for transactions", - ), - &["type"], - ); - let usage = metrics.gauge_int(MetricOpts::new( - "electrs_transactions_cache_size", - "Cache usage for list of transactions (bytes)", - )); - TransactionCache { - map: Mutex::new(SizedLruCache::new(bytes_capacity, lookups, usage)), - } + self.txs.read().unwrap().get(txid).map(f) } - pub fn get_or_else(&self, txid: &Txid, load_txn_func: F) -> Result + pub(crate) fn add_proof(&self, blockhash: BlockHash, txid: Txid, f: F) where - F: FnOnce() -> Result>, + F: FnOnce() -> Proof, { - if let Some(serialized_txn) = self.map.lock().unwrap().get(txid) { - return Ok(deserialize(&serialized_txn).chain_err(|| "failed to parse cached tx")?); - } - let serialized_txn = load_txn_func()?; - let txn = deserialize(&serialized_txn).chain_err(|| "failed to parse serialized tx")?; - let byte_size = 32 /* key (hash size) */ + serialized_txn.len(); - self.map - .lock() + self.proofs + .write() .unwrap() - .put(*txid, serialized_txn, byte_size); - Ok(txn) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bitcoin::hashes::Hash; - - #[test] - fn test_sized_lru_cache_hit_and_miss() { - let counter = CounterVec::new(prometheus::Opts::new("name", "help"), &["type"]).unwrap(); - let usage = IntGauge::new("usage", "help").unwrap(); - let mut cache = SizedLruCache::::new(100, counter.clone(), usage.clone()); - assert_eq!(counter.with_label_values(&["miss"]).get(), 0); - assert_eq!(counter.with_label_values(&["hit"]).get(), 0); - assert_eq!(usage.get(), 0); - - assert_eq!(cache.get(&1), None); // no such key - assert_eq!(counter.with_label_values(&["miss"]).get(), 1); - assert_eq!(counter.with_label_values(&["hit"]).get(), 0); - assert_eq!(usage.get(), 0); - - cache.put(1, 10, 50); // add new key-value - assert_eq!(cache.get(&1), Some(&10)); - assert_eq!(counter.with_label_values(&["miss"]).get(), 1); - assert_eq!(counter.with_label_values(&["hit"]).get(), 1); - assert_eq!(usage.get(), 50); - - cache.put(3, 30, 50); // drop oldest key (1) - cache.put(2, 20, 50); - assert_eq!(cache.get(&1), None); - assert_eq!(cache.get(&2), Some(&20)); - assert_eq!(cache.get(&3), Some(&30)); - assert_eq!(counter.with_label_values(&["miss"]).get(), 2); - assert_eq!(counter.with_label_values(&["hit"]).get(), 3); - assert_eq!(usage.get(), 100); - - cache.put(3, 33, 50); // replace existing value - assert_eq!(cache.get(&1), None); - assert_eq!(cache.get(&2), Some(&20)); - assert_eq!(cache.get(&3), Some(&33)); - assert_eq!(counter.with_label_values(&["miss"]).get(), 3); - assert_eq!(counter.with_label_values(&["hit"]).get(), 5); - assert_eq!(usage.get(), 100); - - cache.put(9, 90, 9999); // larger than cache capacity, don't drop the cache - assert_eq!(cache.get(&1), None); - assert_eq!(cache.get(&2), Some(&20)); - assert_eq!(cache.get(&3), Some(&33)); - assert_eq!(cache.get(&9), None); - assert_eq!(counter.with_label_values(&["miss"]).get(), 5); - assert_eq!(counter.with_label_values(&["hit"]).get(), 7); - assert_eq!(usage.get(), 100); - } - - fn gen_hash(seed: u8) -> T { - let bytes: Vec = (seed..seed + 32).collect(); - ::hash(&bytes[..]) - } - - #[test] - fn test_blocktxids_cache_hit_and_miss() { - let block1: BlockHash = gen_hash(1); - let block2: BlockHash = gen_hash(2); - let block3: BlockHash = gen_hash(3); - let txids: Vec = vec![gen_hash(4), gen_hash(5)]; - - let misses: Mutex = Mutex::new(0); - let miss_func = || { - *misses.lock().unwrap() += 1; - Ok(txids.clone()) - }; - - let dummy_metrics = Metrics::new("127.0.0.1:60000".parse().unwrap()); - // 200 bytes ~ 32 (bytes/hash) * (1 key hash + 2 value hashes) * 2 txns - let cache = BlockTxIDsCache::new(200, &dummy_metrics); - - // cache miss - let result = cache.get_or_else(&block1, &miss_func).unwrap(); - assert_eq!(1, *misses.lock().unwrap()); - assert_eq!(txids, result); - - // cache hit - let result = cache.get_or_else(&block1, &miss_func).unwrap(); - assert_eq!(1, *misses.lock().unwrap()); - assert_eq!(txids, result); - - // cache size is 200, test that blockhash1 falls out of cache - cache.get_or_else(&block2, &miss_func).unwrap(); - assert_eq!(2, *misses.lock().unwrap()); - cache.get_or_else(&block3, &miss_func).unwrap(); - assert_eq!(3, *misses.lock().unwrap()); - cache.get_or_else(&block1, &miss_func).unwrap(); - assert_eq!(4, *misses.lock().unwrap()); - - // cache hits - cache.get_or_else(&block3, &miss_func).unwrap(); - cache.get_or_else(&block1, &miss_func).unwrap(); - assert_eq!(4, *misses.lock().unwrap()); - } - - #[test] - fn test_txn_cache() { - use hex; - - let dummy_metrics = Metrics::new("127.0.0.1:60000".parse().unwrap()); - let cache = TransactionCache::new(1024, &dummy_metrics); - let tx_bytes = hex::decode("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap(); - - let tx: Transaction = deserialize(&tx_bytes).unwrap(); - let txid = tx.txid(); - - let mut misses = 0; - assert_eq!( - cache - .get_or_else(&txid, || { - misses += 1; - Ok(tx_bytes.clone()) - }) - .unwrap(), - tx - ); - assert_eq!(misses, 1); - assert_eq!( - cache - .get_or_else(&txid, || panic!("should not be called")) - .unwrap(), - tx - ); - assert_eq!(misses, 1); + .entry((blockhash, txid)) + .or_insert_with(f); + } + + pub(crate) fn get_proof(&self, blockhash: BlockHash, txid: Txid, f: F) -> Option + where + F: FnOnce(&Proof) -> T, + { + self.proofs.read().unwrap().get(&(blockhash, txid)).map(f) } } diff --git a/src/chain.rs b/src/chain.rs new file mode 100644 index 0000000..f77238d --- /dev/null +++ b/src/chain.rs @@ -0,0 +1,130 @@ +use std::collections::HashMap; + +use bitcoin::consensus::deserialize; +use bitcoin::hashes::hex::FromHex; +use bitcoin::network::constants; +use bitcoin::{BlockHash, BlockHeader}; + +pub(crate) struct NewHeader { + header: BlockHeader, + hash: BlockHash, + height: usize, +} + +impl NewHeader { + pub(crate) fn from((header, height): (BlockHeader, usize)) -> Self { + Self { + header, + hash: header.block_hash(), + height, + } + } + + pub(crate) fn height(&self) -> usize { + self.height + } + + pub(crate) fn hash(&self) -> BlockHash { + self.hash + } +} + +/// Curent blockchain headers' list +pub struct Chain { + headers: Vec<(BlockHash, BlockHeader)>, + heights: HashMap, +} + +impl Chain { + pub fn new(network: constants::Network) -> Self { + let genesis_header_hex = match network { + constants::Network::Bitcoin => "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c", + constants::Network::Testnet => "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae18", + constants::Network::Regtest => "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f2002000000", + constants::Network::Signet => "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a008f4d5fae77031e8ad22203", + }; + let genesis_header_bytes = Vec::from_hex(genesis_header_hex).unwrap(); + let genesis: BlockHeader = deserialize(&genesis_header_bytes).unwrap(); + assert_eq!(genesis.prev_blockhash, BlockHash::default()); + Self { + headers: vec![(genesis.block_hash(), genesis)], + heights: std::iter::once((genesis.block_hash(), 0)).collect(), + } + } + + pub(crate) fn load(&mut self, headers: Vec, tip: BlockHash) { + let genesis_hash = self.headers[0].0; + + let mut header_map: HashMap = + headers.into_iter().map(|h| (h.block_hash(), h)).collect(); + let mut blockhash = tip; + let mut new_headers = vec![]; + while blockhash != genesis_hash { + let header = match header_map.remove(&blockhash) { + Some(header) => header, + None => panic!("missing header {} while loading from DB", blockhash), + }; + blockhash = header.prev_blockhash; + new_headers.push(header); + } + info!("loading {} headers, tip={}", new_headers.len(), tip); + let new_headers = new_headers.into_iter().rev(); // order by height + self.update(new_headers.zip(1..).map(NewHeader::from).collect()) + } + + pub(crate) fn get_block_hash(&self, height: usize) -> Option { + self.headers.get(height).map(|(hash, _header)| *hash) + } + + pub(crate) fn get_block_header(&self, height: usize) -> Option<&BlockHeader> { + self.headers.get(height).map(|(_hash, header)| header) + } + + pub(crate) fn get_block_height(&self, blockhash: &BlockHash) -> Option { + self.heights.get(blockhash).copied() + } + + pub(crate) fn update(&mut self, headers: Vec) { + if let Some(first_height) = headers.first().map(|h| h.height) { + for (hash, _header) in self.headers.drain(first_height..) { + assert!(self.heights.remove(&hash).is_some()); + } + for (h, height) in headers.into_iter().zip(first_height..) { + assert_eq!(h.height, height); + assert_eq!(h.hash, h.header.block_hash()); + assert!(self.heights.insert(h.hash, h.height).is_none()); + self.headers.push((h.hash, h.header)); + } + info!( + "chain updated: tip={}, height={}", + self.headers.last().unwrap().0, + self.headers.len() - 1 + ); + } + } + + pub(crate) fn tip(&self) -> BlockHash { + self.headers.last().expect("empty chain").0 + } + + pub(crate) fn height(&self) -> usize { + self.headers.len() - 1 + } + + pub(crate) fn locator(&self) -> Vec { + let mut result = vec![]; + let mut index = self.headers.len() - 1; + let mut step = 1; + loop { + if result.len() >= 10 { + step *= 2; + } + result.push(self.headers[index].0); + if index == 0 { + break; + } + index = index.saturating_sub(step); + } + result + } +} diff --git a/src/config.rs b/src/config.rs index ec3d040..7d54672 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,18 +1,14 @@ use bitcoin::network::constants::Network; -use dirs::home_dir; -use std::convert::TryInto; +use dirs_next::home_dir; + use std::ffi::{OsStr, OsString}; use std::fmt; -use std::fs; use std::net::SocketAddr; use std::net::ToSocketAddrs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; -use crate::daemon::CookieGetter; -use crate::errors::*; +use std::time::Duration; const DEFAULT_SERVER_ADDRESS: [u8; 4] = [127, 0, 0, 1]; // by default, serve on IPv4 localhost @@ -109,7 +105,7 @@ impl FromStr for BitcoinNetwork { impl ::configure_me::parse_arg::ParseArgFromStr for BitcoinNetwork { fn describe_type(mut writer: W) -> std::fmt::Result { - write!(writer, "either 'bitcoin', 'testnet' or 'regtest'") + write!(writer, "either 'bitcoin', 'testnet', 'regtest' or 'signet'") } } @@ -120,25 +116,21 @@ impl Into for BitcoinNetwork { } /// Parsed and post-processed configuration +#[derive(Debug)] pub struct Config { // See below for the documentation of each field: - pub log: stderrlog::StdErrLog, - pub network_type: Network, + pub network: Network, pub db_path: PathBuf, pub daemon_dir: PathBuf, - pub blocks_dir: PathBuf, + pub daemon_cookie_file: PathBuf, pub daemon_rpc_addr: SocketAddr, + pub daemon_p2p_addr: SocketAddr, pub electrum_rpc_addr: SocketAddr, pub monitoring_addr: SocketAddr, - pub jsonrpc_import: bool, pub wait_duration: Duration, pub index_batch_size: usize, - pub bulk_index_threads: usize, - pub tx_cache_size: usize, - pub txid_limit: usize, pub server_banner: String, - pub blocktxids_cache_size: usize, - pub cookie_getter: Arc, + pub args: Vec, } /// Returns default daemon directory @@ -151,26 +143,6 @@ fn default_daemon_dir() -> PathBuf { home } -fn default_blocks_dir(daemon_dir: &Path) -> PathBuf { - daemon_dir.join("blocks") -} - -fn create_cookie_getter( - cookie: Option, - cookie_file: Option, - daemon_dir: &Path, -) -> Arc { - match (cookie, cookie_file) { - (None, None) => Arc::new(CookieFile::from_daemon_dir(daemon_dir)), - (None, Some(file)) => Arc::new(CookieFile::from_file(file)), - (Some(cookie), None) => Arc::new(StaticCookie::from_string(cookie)), - (Some(_), Some(_)) => { - eprintln!("Error: ambigous configuration - cookie and cookie_file can't be specified at the same time"); - std::process::exit(1); - } - } -} - impl Config { /// Parses args, env vars, config files and post-processes them pub fn from_args() -> Config { @@ -186,36 +158,49 @@ impl Config { .chain(home_config.as_ref().map(AsRef::as_ref)) .chain(std::iter::once(system_config)); - let (mut config, _) = + let (mut config, args) = internal::Config::including_optional_config_files(configs).unwrap_or_exit(); let db_subdir = match config.network { - // We must keep the name "mainnet" due to backwards compatibility - Network::Bitcoin => "mainnet", + Network::Bitcoin => "bitcoin", Network::Testnet => "testnet", Network::Regtest => "regtest", + Network::Signet => "signet", }; config.db_dir.push(db_subdir); - let default_daemon_port = match config.network { + let default_daemon_rpc_port = match config.network { Network::Bitcoin => 8332, Network::Testnet => 18332, Network::Regtest => 18443, + Network::Signet => 38332, + }; + let default_daemon_p2p_port = match config.network { + Network::Bitcoin => 8333, + Network::Testnet => 18333, + Network::Regtest => 18444, + Network::Signet => 38333, }; let default_electrum_port = match config.network { Network::Bitcoin => 50001, Network::Testnet => 60001, Network::Regtest => 60401, + Network::Signet => 60601, }; let default_monitoring_port = match config.network { Network::Bitcoin => 4224, Network::Testnet => 14224, Network::Regtest => 24224, + Network::Signet => 34224, }; let daemon_rpc_addr: SocketAddr = config.daemon_rpc_addr.map_or( - (DEFAULT_SERVER_ADDRESS, default_daemon_port).into(), + (DEFAULT_SERVER_ADDRESS, default_daemon_rpc_port).into(), + ResolvAddr::resolve_or_exit, + ); + let daemon_p2p_addr: SocketAddr = config.daemon_p2p_addr.map_or( + (DEFAULT_SERVER_ADDRESS, default_daemon_p2p_port).into(), ResolvAddr::resolve_or_exit, ); let electrum_rpc_addr: SocketAddr = config.electrum_rpc_addr.map_or( @@ -231,140 +216,31 @@ impl Config { Network::Bitcoin => (), Network::Testnet => config.daemon_dir.push("testnet3"), Network::Regtest => config.daemon_dir.push("regtest"), + Network::Signet => config.daemon_dir.push("signet"), } let daemon_dir = &config.daemon_dir; - let blocks_dir = config - .blocks_dir - .unwrap_or_else(|| default_blocks_dir(daemon_dir)); + let daemon_cookie_file = daemon_dir.join(".cookie"); - let cookie_getter = create_cookie_getter(config.cookie, config.cookie_file, daemon_dir); - - let mut log = stderrlog::new(); - log.verbosity( - config - .verbose - .try_into() - .expect("Overflow: Running electrs on less than 32 bit devices is unsupported"), - ); - log.timestamp(if config.timestamp { - stderrlog::Timestamp::Millisecond - } else { - stderrlog::Timestamp::Off - }); - log.init().unwrap_or_else(|err| { - eprintln!("Error: logging initialization failed: {}", err); - std::process::exit(1) - }); - // Could have been default, but it's useful to allow the user to specify 0 when overriding - // configs. - if config.bulk_index_threads == 0 { - config.bulk_index_threads = num_cpus::get(); - } - const MB: f32 = (1 << 20) as f32; let config = Config { - log, - network_type: config.network, + network: config.network, db_path: config.db_dir, daemon_dir: config.daemon_dir, - blocks_dir, + daemon_cookie_file, daemon_rpc_addr, + daemon_p2p_addr, electrum_rpc_addr, monitoring_addr, - jsonrpc_import: config.jsonrpc_import, wait_duration: Duration::from_secs(config.wait_duration_secs), index_batch_size: config.index_batch_size, - bulk_index_threads: config.bulk_index_threads, - tx_cache_size: (config.tx_cache_size_mb * MB) as usize, - blocktxids_cache_size: (config.blocktxids_cache_size_mb * MB) as usize, - txid_limit: config.txid_limit, server_banner: config.server_banner, - cookie_getter, + args: args.map(|a| a.into_string().unwrap()).collect(), }; eprintln!("{:?}", config); + env_logger::Builder::from_default_env() + .default_format() + .format_timestamp_millis() + .init(); config } - - pub fn cookie_getter(&self) -> Arc { - Arc::clone(&self.cookie_getter) - } -} - -// CookieGetter + Debug isn't implemented in Rust, so we have to skip cookie_getter -macro_rules! debug_struct { - ($name:ty, $($field:ident,)*) => { - impl fmt::Debug for $name { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct(stringify!($name)) - $( - .field(stringify!($field), &self.$field) - )* - .finish() - } - } - } -} - -debug_struct! { Config, - log, - network_type, - db_path, - daemon_dir, - blocks_dir, - daemon_rpc_addr, - electrum_rpc_addr, - monitoring_addr, - jsonrpc_import, - index_batch_size, - bulk_index_threads, - tx_cache_size, - txid_limit, - server_banner, - blocktxids_cache_size, -} - -struct StaticCookie { - value: Vec, -} - -impl StaticCookie { - fn from_string(value: String) -> Self { - StaticCookie { - value: value.into(), - } - } -} - -impl CookieGetter for StaticCookie { - fn get(&self) -> Result> { - Ok(self.value.clone()) - } -} - -struct CookieFile { - cookie_file: PathBuf, -} - -impl CookieFile { - fn from_daemon_dir(daemon_dir: &Path) -> Self { - CookieFile { - cookie_file: daemon_dir.join(".cookie"), - } - } - - fn from_file(cookie_file: PathBuf) -> Self { - CookieFile { cookie_file } - } -} - -impl CookieGetter for CookieFile { - fn get(&self) -> Result> { - let contents = fs::read(&self.cookie_file).chain_err(|| { - ErrorKind::Connection(format!( - "failed to read cookie from {}", - self.cookie_file.display() - )) - })?; - Ok(contents) - } } diff --git a/src/daemon.rs b/src/daemon.rs index 0a0a273..c02ae10 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,666 +1,298 @@ -use bitcoin::blockdata::block::{Block, BlockHeader}; -use bitcoin::blockdata::transaction::Transaction; -use bitcoin::consensus::encode::{deserialize, serialize}; -use bitcoin::hash_types::{BlockHash, Txid}; -use bitcoin::network::constants::Network; -use bitcoin::hashes::hex::{FromHex, ToHex}; -use bitcoin::hashes::Hash; -use serde_json::{from_str, from_value, Map, Value}; -use std::collections::{HashMap, HashSet}; -use std::io::{BufRead, BufReader, Lines, Write}; -use std::net::{SocketAddr, TcpStream}; -use std::path::PathBuf; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::{Arc, Mutex}; -use std::time::Duration; +use anyhow::{Context, Result}; -use crate::cache::BlockTxIDsCache; -use crate::errors::*; -use crate::metrics::{HistogramOpts, HistogramVec, Metrics}; -use crate::signal::Waiter; -use crate::util::HeaderList; +use std::io::Write; +use std::iter::FromIterator; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; +use std::sync::Mutex; +use std::time::{SystemTime, UNIX_EPOCH}; -fn parse_hash(value: &Value) -> Result { - Ok(T::from_hex( - value - .as_str() - .chain_err(|| format!("non-string value: {}", value))?, - ) - .chain_err(|| format!("non-hex value: {}", value))?) -} +use bitcoin::consensus::encode; +use bitcoin::network::stream_reader::StreamReader; +use bitcoin::network::{ + address, constants, + message::{self, NetworkMessage}, + message_blockdata::{GetHeadersMessage, Inventory}, + message_network, +}; +use bitcoin::secp256k1; +use bitcoin::secp256k1::rand::Rng; +use bitcoin::{Amount, Block, BlockHash, Network, Transaction, Txid}; +use bitcoincore_rpc::{self, json, RpcApi}; -fn header_from_value(value: Value) -> Result { - let header_hex = value - .as_str() - .chain_err(|| format!("non-string header: {}", value))?; - let header_bytes = hex::decode(header_hex).chain_err(|| "non-hex header")?; - Ok( - deserialize(&header_bytes) - .chain_err(|| format!("failed to parse header {}", header_hex))?, - ) -} - -fn block_from_value(value: Value) -> Result { - let block_hex = value.as_str().chain_err(|| "non-string block")?; - let block_bytes = hex::decode(block_hex).chain_err(|| "non-hex block")?; - Ok(deserialize(&block_bytes).chain_err(|| format!("failed to parse block {}", block_hex))?) -} - -fn tx_from_value(value: Value) -> Result { - let tx_hex = value.as_str().chain_err(|| "non-string tx")?; - let tx_bytes = hex::decode(tx_hex).chain_err(|| "non-hex tx")?; - Ok(deserialize(&tx_bytes).chain_err(|| format!("failed to parse tx {}", tx_hex))?) -} - -/// Parse JSONRPC error code, if exists. -fn parse_error_code(err: &Value) -> Option { - if err.is_null() { - return None; - } - err.as_object()?.get("code")?.as_i64() -} - -fn check_error_code(reply_obj: &Map, method: &str) -> Result<()> { - if let Some(err) = reply_obj.get("error") { - if let Some(code) = parse_error_code(&err) { - match code { - // RPC_IN_WARMUP -> retry by later reconnection - -28 => bail!(ErrorKind::Connection(err.to_string())), - _ => bail!("{} RPC error: {}", method, err), - } - } - } - Ok(()) -} - -fn parse_jsonrpc_reply(mut reply: Value, method: &str, expected_id: u64) -> Result { - if let Some(reply_obj) = reply.as_object_mut() { - check_error_code(reply_obj, method)?; - let id = reply_obj - .get("id") - .chain_err(|| format!("no id in reply: {:?}", reply_obj))? - .clone(); - if id != expected_id { - bail!( - "wrong {} response id {}, expected {}", - method, - id, - expected_id - ); - } - if let Some(result) = reply_obj.get_mut("result") { - return Ok(result.take()); - } - bail!("no result in reply: {:?}", reply_obj); - } - bail!("non-object reply: {:?}", reply); -} - -#[derive(Serialize, Deserialize, Debug)] -struct BlockchainInfo { - chain: String, - blocks: u32, - headers: u32, - verificationprogress: f64, - bestblockhash: String, - pruned: bool, - initialblockdownload: bool, -} - -#[derive(Serialize, Deserialize, Debug)] -struct NetworkInfo { - version: u64, - subversion: String, - relayfee: f64, // in BTC -} - -pub struct MempoolEntry { - fee: u64, // in satoshis - vsize: u32, // in virtual bytes (= weight/4) - fee_per_vbyte: f32, -} - -impl MempoolEntry { - fn new(fee: u64, vsize: u32) -> MempoolEntry { - MempoolEntry { - fee, - vsize, - fee_per_vbyte: fee as f32 / vsize as f32, - } - } - - pub fn fee_per_vbyte(&self) -> f32 { - self.fee_per_vbyte - } - - pub fn fee(&self) -> u64 { - self.fee - } - - pub fn vsize(&self) -> u32 { - self.vsize - } -} - -pub trait CookieGetter: Send + Sync { - fn get(&self) -> Result>; -} +use crate::{ + chain::{Chain, NewHeader}, + config::Config, +}; struct Connection { - tx: TcpStream, - rx: Lines>, - cookie_getter: Arc, - addr: SocketAddr, - signal: Waiter, -} - -fn tcp_connect(addr: SocketAddr, signal: &Waiter) -> Result { - loop { - match TcpStream::connect(addr) { - Ok(conn) => return Ok(conn), - Err(err) => { - warn!("failed to connect daemon at {}: {}", addr, err); - signal.wait(Duration::from_secs(3))?; - continue; - } - } - } + stream: TcpStream, + reader: StreamReader, + network: Network, } impl Connection { - fn new( - addr: SocketAddr, - cookie_getter: Arc, - signal: Waiter, - ) -> Result { - let conn = tcp_connect(addr, &signal)?; - let reader = BufReader::new( - conn.try_clone() - .chain_err(|| format!("failed to clone {:?}", conn))?, + pub fn connect(network: Network, address: SocketAddr) -> Result { + let stream = TcpStream::connect(address) + .with_context(|| format!("{} p2p failed to connect: {:?}", network, address))?; + let reader = StreamReader::new( + stream.try_clone().context("stream failed to clone")?, + /*buffer_size*/ Some(1 << 20), ); - Ok(Connection { - tx: conn, - rx: reader.lines(), - cookie_getter, - addr, - signal, - }) + let mut conn = Self { + stream, + reader, + network, + }; + conn.send(build_version_message())?; + if let NetworkMessage::GetHeaders(_) = conn.recv()? { + conn.send(NetworkMessage::Headers(vec![]))?; + } + Ok(conn) } - fn reconnect(&self) -> Result { - Connection::new(self.addr, self.cookie_getter.clone(), self.signal.clone()) + fn send(&mut self, msg: NetworkMessage) -> Result<()> { + trace!("send: {:?}", msg); + let raw_msg = message::RawNetworkMessage { + magic: self.network.magic(), + payload: msg, + }; + self.stream + .write_all(encode::serialize(&raw_msg).as_slice()) + .context("p2p failed to send") } - fn send(&mut self, request: &str) -> Result<()> { - let cookie = &self.cookie_getter.get()?; - let msg = format!( - "POST / HTTP/1.1\nAuthorization: Basic {}\nContent-Length: {}\n\n{}", - base64::encode(cookie), - request.len(), - request, - ); - self.tx.write_all(msg.as_bytes()).chain_err(|| { - ErrorKind::Connection("disconnected from daemon while sending".to_owned()) - }) - } + fn recv(&mut self) -> Result { + loop { + let raw_msg: message::RawNetworkMessage = + self.reader.read_next().context("p2p failed to recv")?; - fn recv(&mut self) -> Result { - // TODO: use proper HTTP parser. - let mut in_header = true; - let mut contents: Option = None; - let iter = self.rx.by_ref(); - let status = iter - .next() - .chain_err(|| { - ErrorKind::Connection("disconnected from daemon while receiving".to_owned()) - })? - .chain_err(|| "failed to read status")?; - let mut headers = HashMap::new(); - for line in iter { - let line = line.chain_err(|| ErrorKind::Connection("failed to read".to_owned()))?; - if line.is_empty() { - in_header = false; // next line should contain the actual response. - } else if in_header { - let parts: Vec<&str> = line.splitn(2, ": ").collect(); - if parts.len() == 2 { - headers.insert(parts[0].to_owned(), parts[1].to_owned()); - } else { - warn!("invalid header: {:?}", line); + trace!("recv: {:?}", raw_msg.payload); + match raw_msg.payload { + NetworkMessage::Version(version) => { + trace!("peer version: {:?}", version); + self.send(NetworkMessage::Verack)?; } - } else { - contents = Some(line); - break; + NetworkMessage::Ping(nonce) => { + self.send(NetworkMessage::Pong(nonce))?; + } + NetworkMessage::Verack + | NetworkMessage::Alert(_) + | NetworkMessage::Addr(_) + | NetworkMessage::Inv(_) => {} + payload => return Ok(payload), + }; + } + } +} + +pub(crate) fn rpc_connect(config: &Config) -> Result { + let rpc_url = format!("http://{}", config.daemon_rpc_addr); + if !config.daemon_cookie_file.exists() { + bail!("{:?} is missing", config.daemon_cookie_file); + } + let rpc_auth = bitcoincore_rpc::Auth::CookieFile(config.daemon_cookie_file.clone()); + let rpc = bitcoincore_rpc::Client::new(rpc_url, rpc_auth) + .with_context(|| format!("failed to connect to RPC: {}", config.daemon_rpc_addr))?; + + use bitcoincore_rpc::{ + jsonrpc::error::Error::Rpc as ServerError, Error::JsonRpc as JsonRpcError, + }; + loop { + match rpc.get_blockchain_info() { + Ok(info) => { + if info.blocks < info.headers { + info!( + "waiting for {} blocks to download", + info.headers - info.blocks + ); + std::thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + } + Err(err) => { + if let JsonRpcError(ServerError(ref e)) = err { + if e.code == -28 { + info!("waiting for RPC warmup: {}", e.message); + std::thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + } + return Err(err).context("daemon not available"); } } - - let contents = - contents.chain_err(|| ErrorKind::Connection("no reply from daemon".to_owned()))?; - let contents_length: &str = headers - .get("Content-Length") - .chain_err(|| format!("Content-Length is missing: {:?}", headers))?; - let contents_length: usize = contents_length - .parse() - .chain_err(|| format!("invalid Content-Length: {:?}", contents_length))?; - - let expected_length = contents_length - 1; // trailing EOL is skipped - if expected_length != contents.len() { - bail!(ErrorKind::Connection(format!( - "expected {} bytes, got {}", - expected_length, - contents.len() - ))); - } - - Ok(if status == "HTTP/1.1 200 OK" { - contents - } else if status == "HTTP/1.1 500 Internal Server Error" { - warn!("HTTP status: {}", status); - contents // the contents should have a JSONRPC error field - } else { - bail!( - "request failed {:?}: {:?} = {:?}", - status, - headers, - contents - ); - }) - } -} - -struct Counter { - value: AtomicU64, -} - -impl Counter { - fn new() -> Self { - Counter { value: 0.into() } - } - - fn next(&self) -> u64 { - // fetch_add() returns previous value, we want current one - self.value.fetch_add(1, Ordering::Relaxed) + 1 + return Ok(rpc); } } pub struct Daemon { - daemon_dir: PathBuf, - blocks_dir: PathBuf, - network: Network, - conn: Mutex, - message_id: Counter, // for monotonic JSONRPC 'id' - signal: Waiter, - blocktxids_cache: Arc, - - // monitoring - latency: HistogramVec, - size: HistogramVec, + p2p: Mutex, + rpc: bitcoincore_rpc::Client, } impl Daemon { - pub fn new( - daemon_dir: &PathBuf, - blocks_dir: &PathBuf, - daemon_rpc_addr: SocketAddr, - cookie_getter: Arc, - network: Network, - signal: Waiter, - blocktxids_cache: Arc, - metrics: &Metrics, - ) -> Result { - let daemon = Daemon { - daemon_dir: daemon_dir.clone(), - blocks_dir: blocks_dir.clone(), - network, - conn: Mutex::new(Connection::new( - daemon_rpc_addr, - cookie_getter, - signal.clone(), - )?), - message_id: Counter::new(), - blocktxids_cache, - signal: signal.clone(), - latency: metrics.histogram_vec( - HistogramOpts::new("electrs_daemon_rpc", "Bitcoind RPC latency (in seconds)"), - &["method"], - ), - // TODO: use better buckets (e.g. 1 byte to 10MB). - size: metrics.histogram_vec( - HistogramOpts::new("electrs_daemon_bytes", "Bitcoind RPC size (in bytes)"), - &["method", "dir"], - ), - }; - let network_info = daemon.getnetworkinfo()?; - info!("{:?}", network_info); - if network_info.version < 16_00_00 { - bail!( - "{} is not supported - please use bitcoind 0.16+", - network_info.subversion, - ) + pub fn connect(config: &Config) -> Result { + let rpc = rpc_connect(config)?; + let network_info = rpc.get_network_info()?; + if network_info.version < 21_00_00 { + bail!("electrs requires bitcoind 0.21+"); } - let blockchain_info = daemon.getblockchaininfo()?; - info!("{:?}", blockchain_info); + if !network_info.network_active { + bail!("electrs requires active bitcoind p2p network"); + } + let blockchain_info = rpc.get_blockchain_info()?; if blockchain_info.pruned { - bail!("pruned node is not supported (use '-prune=0' bitcoind flag)".to_owned()) + bail!("electrs requires non-pruned bitcoind node"); } - loop { - let info = daemon.getblockchaininfo()?; - if !info.initialblockdownload { - break; - } - if network == Network::Regtest && info.headers == info.blocks { - break; - } - warn!( - "wait until IBD is over: headers={} blocks={} progress={}", - info.headers, info.blocks, info.verificationprogress - ); - signal.wait(Duration::from_secs(3))?; - } - Ok(daemon) + let p2p = Mutex::new(Connection::connect(config.network, config.daemon_p2p_addr)?); + Ok(Self { p2p, rpc }) } - pub fn reconnect(&self) -> Result { - Ok(Daemon { - daemon_dir: self.daemon_dir.clone(), - blocks_dir: self.blocks_dir.clone(), - network: self.network, - conn: Mutex::new(self.conn.lock().unwrap().reconnect()?), - message_id: Counter::new(), - signal: self.signal.clone(), - blocktxids_cache: Arc::clone(&self.blocktxids_cache), - latency: self.latency.clone(), - size: self.size.clone(), - }) + pub(crate) fn estimate_fee(&self, nblocks: u16) -> Result> { + Ok(self + .rpc + .estimate_smart_fee(nblocks, None) + .context("failed to estimate fee")? + .fee_rate) } - pub fn list_blk_files(&self) -> Result> { - let path = self.blocks_dir.join("blk*.dat"); - info!("listing block files at {:?}", path); - let mut paths: Vec = glob::glob(path.to_str().unwrap()) - .chain_err(|| "failed to list blk*.dat files")? - .map(std::result::Result::unwrap) - .collect(); - paths.sort(); - Ok(paths) + pub(crate) fn get_relay_fee(&self) -> Result { + Ok(self + .rpc + .get_network_info() + .context("failed to get relay fee")? + .relay_fee) } - pub fn magic(&self) -> u32 { - self.network.magic() + pub(crate) fn broadcast(&self, tx: &Transaction) -> Result { + self.rpc + .send_raw_transaction(tx) + .context("failed to broadcast transaction") } - fn call_jsonrpc(&self, method: &str, request: &Value) -> Result { - let mut conn = self.conn.lock().unwrap(); - let timer = self.latency.with_label_values(&[method]).start_timer(); - let request = request.to_string(); - conn.send(&request)?; - self.size - .with_label_values(&[method, "send"]) - .observe(request.len() as f64); - let response = conn.recv()?; - let result: Value = from_str(&response).chain_err(|| "invalid JSON")?; - timer.observe_duration(); - self.size - .with_label_values(&[method, "recv"]) - .observe(response.len() as f64); - Ok(result) - } - - fn handle_request_batch(&self, method: &str, params_list: &[Value]) -> Result> { - let id = self.message_id.next(); - let reqs = params_list - .iter() - .map(|params| json!({"method": method, "params": params, "id": id})) - .collect(); - let mut results = vec![]; - let mut replies = self.call_jsonrpc(method, &reqs)?; - if let Some(replies_vec) = replies.as_array_mut() { - for reply in replies_vec { - results.push(parse_jsonrpc_reply(reply.take(), method, id)?) - } - return Ok(results); - } - bail!("non-array replies: {:?}", replies); - } - - fn retry_request_batch(&self, method: &str, params_list: &[Value]) -> Result> { - loop { - match self.handle_request_batch(method, params_list) { - Err(Error(ErrorKind::Connection(msg), _)) => { - warn!("reconnecting to bitcoind: {}", msg); - self.signal.wait(Duration::from_secs(3))?; - let mut conn = self.conn.lock().unwrap(); - *conn = conn.reconnect()?; - continue; - } - result => return result, - } - } - } - - fn request(&self, method: &str, params: Value) -> Result { - let mut values = self.retry_request_batch(method, &[params])?; - assert_eq!(values.len(), 1); - Ok(values.remove(0)) - } - - fn requests(&self, method: &str, params_list: &[Value]) -> Result> { - self.retry_request_batch(method, params_list) - } - - // bitcoind JSONRPC API: - - fn getblockchaininfo(&self) -> Result { - let info: Value = self.request("getblockchaininfo", json!([]))?; - Ok(from_value(info).chain_err(|| "invalid blockchain info")?) - } - - fn getnetworkinfo(&self) -> Result { - let info: Value = self.request("getnetworkinfo", json!([]))?; - Ok(from_value(info).chain_err(|| "invalid network info")?) - } - - pub fn get_subversion(&self) -> Result { - Ok(self.getnetworkinfo()?.subversion) - } - - pub fn get_relayfee(&self) -> Result { - Ok(self.getnetworkinfo()?.relayfee) - } - - pub fn getbestblockhash(&self) -> Result { - parse_hash(&self.request("getbestblockhash", json!([]))?).chain_err(|| "invalid blockhash") - } - - pub fn getblockheader(&self, blockhash: &BlockHash) -> Result { - header_from_value(self.request( - "getblockheader", - json!([blockhash.to_hex(), /*verbose=*/ false]), - )?) - } - - pub fn getblockheaders(&self, heights: &[usize]) -> Result> { - let heights: Vec = heights.iter().map(|height| json!([height])).collect(); - let params_list: Vec = self - .requests("getblockhash", &heights)? - .into_iter() - .map(|hash| json!([hash, /*verbose=*/ false])) - .collect(); - let mut result = vec![]; - for h in self.requests("getblockheader", ¶ms_list)? { - result.push(header_from_value(h)?); - } - Ok(result) - } - - pub fn getblock(&self, blockhash: &BlockHash) -> Result { - let block = block_from_value( - self.request("getblock", json!([blockhash.to_hex(), /*verbose=*/ false]))?, - )?; - assert_eq!(block.block_hash(), *blockhash); - Ok(block) - } - - fn load_blocktxids(&self, blockhash: &BlockHash) -> Result> { - self.request("getblock", json!([blockhash.to_hex(), /*verbose=*/ 1]))? - .get("tx") - .chain_err(|| "block missing txids")? - .as_array() - .chain_err(|| "invalid block txids")? - .iter() - .map(parse_hash) - .collect::>>() - } - - pub fn getblocktxids(&self, blockhash: &BlockHash) -> Result> { - self.blocktxids_cache - .get_or_else(&blockhash, || self.load_blocktxids(blockhash)) - } - - pub fn getblocks(&self, blockhashes: &[BlockHash]) -> Result> { - let params_list: Vec = blockhashes - .iter() - .map(|hash| json!([hash.to_hex(), /*verbose=*/ false])) - .collect(); - let values = self.requests("getblock", ¶ms_list)?; - let mut blocks = vec![]; - for value in values { - blocks.push(block_from_value(value)?); - } - Ok(blocks) - } - - pub fn gettransaction( + pub(crate) fn get_transaction_info( &self, - txhash: &Txid, + txid: &Txid, + blockhash: Option, + ) -> Result { + self.rpc + .get_raw_transaction_info(txid, blockhash.as_ref()) + .context("failed to get transaction info") + } + + pub(crate) fn get_transaction_hex( + &self, + txid: &Txid, + blockhash: Option, + ) -> Result { + self.rpc + .get_raw_transaction_info(txid, blockhash.as_ref()) + .context("failed to get transaction info") + } + + pub(crate) fn get_transaction( + &self, + txid: &Txid, blockhash: Option, ) -> Result { - let mut args = json!([txhash.to_hex(), /*verbose=*/ false]); - if let Some(blockhash) = blockhash { - args.as_array_mut().unwrap().push(json!(blockhash.to_hex())); - } - tx_from_value(self.request("getrawtransaction", args)?) + self.rpc + .get_raw_transaction(txid, blockhash.as_ref()) + .context("failed to get transaction") } - pub fn gettransaction_raw( - &self, - txhash: &Txid, - blockhash: Option, - verbose: bool, - ) -> Result { - let mut args = json!([txhash.to_hex(), verbose]); - if let Some(blockhash) = blockhash { - args.as_array_mut().unwrap().push(json!(blockhash.to_hex())); - } - Ok(self.request("getrawtransaction", args)?) + pub(crate) fn get_block_txids(&self, blockhash: BlockHash) -> Result> { + Ok(self + .rpc + .get_block_info(&blockhash) + .context("failed to get block txids")? + .tx) } - pub fn gettransactions(&self, txhashes: &[&Txid]) -> Result> { - let params_list: Vec = txhashes + pub(crate) fn get_mempool_txids(&self) -> Result> { + self.rpc + .get_raw_mempool() + .context("failed to get mempool txids") + } + + pub(crate) fn get_mempool_entry(&self, txid: &Txid) -> Result { + self.rpc + .get_mempool_entry(txid) + .context("failed to get mempool entry") + } + + pub(crate) fn get_new_headers(&self, chain: &Chain) -> Result> { + let mut conn = self.p2p.lock().unwrap(); + + let msg = GetHeadersMessage::new(chain.locator(), BlockHash::default()); + conn.send(NetworkMessage::GetHeaders(msg))?; + let headers = match conn.recv()? { + NetworkMessage::Headers(headers) => headers, + msg => bail!("unexpected {:?}", msg), + }; + + debug!("got {} new headers", headers.len()); + let prev_blockhash = match headers.first().map(|h| h.prev_blockhash) { + None => return Ok(vec![]), + Some(prev_blockhash) => prev_blockhash, + }; + let new_heights = match chain.get_block_height(&prev_blockhash) { + Some(last_height) => (last_height + 1).., + None => bail!("missing prev_blockhash: {}", prev_blockhash), + }; + Ok(headers + .into_iter() + .zip(new_heights) + .map(NewHeader::from) + .collect()) + } + + pub(crate) fn for_blocks(&self, blockhashes: B, mut func: F) -> Result<()> + where + B: IntoIterator, + F: FnMut(BlockHash, Block), + { + let mut conn = self.p2p.lock().unwrap(); + + let blockhashes = Vec::from_iter(blockhashes); + if blockhashes.is_empty() { + return Ok(()); + } + let inv = blockhashes .iter() - .map(|txhash| json!([txhash.to_hex(), /*verbose=*/ false])) + .map(|h| Inventory::WitnessBlock(*h)) .collect(); - - let values = self.requests("getrawtransaction", ¶ms_list)?; - let mut txs = vec![]; - for value in values { - txs.push(tx_from_value(value)?); + debug!("loading {} blocks", blockhashes.len()); + conn.send(NetworkMessage::GetData(inv))?; + for hash in blockhashes { + match conn.recv()? { + NetworkMessage::Block(block) => { + assert_eq!(block.block_hash(), hash, "got unexpected block"); + func(hash, block); + } + msg => bail!("unexpected {:?}", msg), + }; } - assert_eq!(txhashes.len(), txs.len()); - Ok(txs) - } - - pub fn getmempooltxids(&self) -> Result> { - let txids: Value = self.request("getrawmempool", json!([/*verbose=*/ false]))?; - let mut result = HashSet::new(); - for value in txids.as_array().chain_err(|| "non-array result")? { - result.insert(parse_hash(&value).chain_err(|| "invalid txid")?); - } - Ok(result) - } - - pub fn getmempoolentry(&self, txid: &Txid) -> Result { - let entry = self.request("getmempoolentry", json!([txid.to_hex()]))?; - let fee = (entry - .get("fee") - .chain_err(|| "missing fee")? - .as_f64() - .chain_err(|| "non-float fee")? - * 100_000_000f64) as u64; - let vsize = entry - .get("size") - .or_else(|| entry.get("vsize")) // (https://github.com/bitcoin/bitcoin/pull/15637) - .chain_err(|| "missing vsize")? - .as_u64() - .chain_err(|| "non-integer vsize")? as u32; - Ok(MempoolEntry::new(fee, vsize)) - } - - pub fn broadcast(&self, tx: &Transaction) -> Result { - let tx = hex::encode(serialize(tx)); - let txid = self.request("sendrawtransaction", json!([tx]))?; - Ok( - Txid::from_hex(txid.as_str().chain_err(|| "non-string txid")?) - .chain_err(|| "failed to parse txid")?, - ) - } - - fn get_all_headers(&self, tip: &BlockHash) -> Result> { - let info: Value = self.request("getblockheader", json!([tip.to_hex()]))?; - let tip_height = info - .get("height") - .expect("missing height") - .as_u64() - .expect("non-numeric height") as usize; - let all_heights: Vec = (0..=tip_height).collect(); - let chunk_size = 100_000; - let mut result = vec![]; - let null_hash = BlockHash::default(); - for heights in all_heights.chunks(chunk_size) { - trace!("downloading {} block headers", heights.len()); - let mut headers = self.getblockheaders(&heights)?; - assert!(headers.len() == heights.len()); - result.append(&mut headers); - } - - let mut blockhash = null_hash; - for header in &result { - assert_eq!(header.prev_blockhash, blockhash); - blockhash = header.block_hash(); - } - assert_eq!(blockhash, *tip); - Ok(result) - } - - // Returns a list of BlockHeaders in ascending height (i.e. the tip is last). - pub fn get_new_headers( - &self, - indexed_headers: &HeaderList, - bestblockhash: &BlockHash, - ) -> Result> { - // Iterate back over headers until known blockash is found: - if indexed_headers.is_empty() { - return self.get_all_headers(bestblockhash); - } - debug!( - "downloading new block headers ({} already indexed) from {}", - indexed_headers.len(), - bestblockhash, - ); - let mut new_headers = vec![]; - let null_hash = BlockHash::default(); - let mut blockhash = *bestblockhash; - while blockhash != null_hash { - if indexed_headers.header_by_blockhash(&blockhash).is_some() { - break; - } - let header = self - .getblockheader(&blockhash) - .chain_err(|| format!("failed to get {} header", blockhash))?; - new_headers.push(header); - blockhash = header.prev_blockhash; - } - trace!("downloaded {} block headers", new_headers.len()); - new_headers.reverse(); // so the tip is the last vector entry - Ok(new_headers) + Ok(()) } } + +fn build_version_message() -> NetworkMessage { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time error") + .as_secs() as i64; + + let services = constants::ServiceFlags::NETWORK | constants::ServiceFlags::WITNESS; + + NetworkMessage::Version(message_network::VersionMessage { + version: constants::PROTOCOL_VERSION, + services, + timestamp, + receiver: address::Address::new(&addr, services), + sender: address::Address::new(&addr, services), + nonce: secp256k1::rand::thread_rng().gen(), + user_agent: String::from("electrs"), + start_height: 0, + relay: false, + }) +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..4f0e80a --- /dev/null +++ b/src/db.rs @@ -0,0 +1,276 @@ +use anyhow::{Context, Result}; + +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; + +pub(crate) type Row = Box<[u8]>; + +#[derive(Default)] +pub(crate) struct WriteBatch { + pub(crate) tip_row: Row, + pub(crate) header_rows: Vec, + pub(crate) funding_rows: Vec, + pub(crate) spending_rows: Vec, + pub(crate) txid_rows: Vec, +} + +impl WriteBatch { + pub(crate) fn sort(&mut self) { + self.header_rows.sort_unstable(); + self.funding_rows.sort_unstable(); + self.spending_rows.sort_unstable(); + self.txid_rows.sort_unstable(); + } +} + +#[derive(Debug)] +struct Options { + path: PathBuf, +} + +/// RocksDB wrapper for index storage +pub struct DBStore { + db: rocksdb::DB, + path: PathBuf, + bulk_import: AtomicBool, + cfs: Vec<&'static str>, +} + +const CONFIG_CF: &str = "config"; +const HEADERS_CF: &str = "headers"; +const TXID_CF: &str = "txid"; +const FUNDING_CF: &str = "funding"; +const SPENDING_CF: &str = "spending"; + +const CONFIG_KEY: &str = "C"; +const TIP_KEY: &[u8] = b"T"; + +#[derive(Debug, Deserialize, Serialize)] +struct Config { + compacted: bool, + format: u64, +} + +const CURRENT_FORMAT: u64 = 0; + +fn default_opts() -> rocksdb::Options { + let mut opts = rocksdb::Options::default(); + opts.set_keep_log_file_num(10); + opts.set_max_open_files(16); + opts.set_compaction_style(rocksdb::DBCompactionStyle::Level); + opts.set_compression_type(rocksdb::DBCompressionType::Zstd); + opts.set_target_file_size_base(256 << 20); + opts.set_write_buffer_size(256 << 20); + opts.set_disable_auto_compactions(true); // for initial bulk load + opts.set_advise_random_on_open(false); // bulk load uses sequential I/O + opts.set_prefix_extractor(rocksdb::SliceTransform::create_fixed_prefix(8)); + opts +} + +impl DBStore { + /// Opens a new RocksDB at the specified location. + pub fn open(path: &Path) -> Result { + let cfs = vec![CONFIG_CF, HEADERS_CF, TXID_CF, FUNDING_CF, SPENDING_CF]; + let cf_descriptors: Vec = cfs + .iter() + .map(|&name| rocksdb::ColumnFamilyDescriptor::new(name, default_opts())) + .collect(); + + let mut db_opts = default_opts(); + db_opts.create_if_missing(true); + db_opts.create_missing_column_families(true); + let db = rocksdb::DB::open_cf_descriptors(&db_opts, path, cf_descriptors) + .with_context(|| format!("failed to open DB: {:?}", path))?; + let live_files = db.live_files()?; + info!( + "{:?}: {} SST files, {} GB, {} Grows", + path, + live_files.len(), + live_files.iter().map(|f| f.size).sum::() as f64 / 1e9, + live_files.iter().map(|f| f.num_entries).sum::() as f64 / 1e9 + ); + let store = DBStore { + db, + path: path.to_path_buf(), + cfs, + bulk_import: AtomicBool::new(true), + }; + + let config = store.get_config(); + debug!("DB {:?}", config); + if config.format != CURRENT_FORMAT { + bail!("unsupported DB format {}, re-index required", config.format); + } + if config.compacted { + store.start_compactions(); + } + store.set_config(config); + Ok(store) + } + + fn config_cf(&self) -> &rocksdb::ColumnFamily { + self.db.cf_handle(CONFIG_CF).expect("missing CONFIG_CF") + } + + fn funding_cf(&self) -> &rocksdb::ColumnFamily { + self.db.cf_handle(FUNDING_CF).expect("missing FUNDING_CF") + } + + fn spending_cf(&self) -> &rocksdb::ColumnFamily { + self.db.cf_handle(SPENDING_CF).expect("missing SPENDING_CF") + } + + fn txid_cf(&self) -> &rocksdb::ColumnFamily { + self.db.cf_handle(TXID_CF).expect("missing TXID_CF") + } + + fn headers_cf(&self) -> &rocksdb::ColumnFamily { + self.db.cf_handle(HEADERS_CF).expect("missing HEADERS_CF") + } + + pub(crate) fn iter_funding(&self, prefix: Row) -> ScanIterator { + self.iter_prefix_cf(self.funding_cf(), prefix) + } + + pub(crate) fn iter_spending(&self, prefix: Row) -> ScanIterator { + self.iter_prefix_cf(self.spending_cf(), prefix) + } + + pub(crate) fn iter_txid(&self, prefix: Row) -> ScanIterator { + self.iter_prefix_cf(self.txid_cf(), prefix) + } + + fn iter_prefix_cf<'a>(&'a self, cf: &rocksdb::ColumnFamily, prefix: Row) -> ScanIterator<'a> { + let mode = rocksdb::IteratorMode::From(&prefix, rocksdb::Direction::Forward); + let iter = self.db.iterator_cf(cf, mode); + ScanIterator { + prefix, + iter, + done: false, + } + } + + pub(crate) fn read_headers(&self) -> Vec { + let mut opts = rocksdb::ReadOptions::default(); + opts.fill_cache(false); + self.db + .iterator_cf_opt(self.headers_cf(), opts, rocksdb::IteratorMode::Start) + .map(|(key, _)| key) + .filter(|key| &key[..] != TIP_KEY) // headers' rows are longer than TIP_KEY + .collect() + } + + pub(crate) fn get_tip(&self) -> Option> { + self.db + .get_cf(self.headers_cf(), TIP_KEY) + .expect("get_tip failed") + } + + pub(crate) fn write(&self, batch: WriteBatch) -> usize { + let mut db_batch = rocksdb::WriteBatch::default(); + let mut total_rows_count = 0; + for key in batch.funding_rows { + db_batch.put_cf(self.funding_cf(), key, b""); + total_rows_count += 1; + } + for key in batch.spending_rows { + db_batch.put_cf(self.spending_cf(), key, b""); + total_rows_count += 1; + } + for key in batch.txid_rows { + db_batch.put_cf(self.txid_cf(), key, b""); + total_rows_count += 1; + } + for key in batch.header_rows { + db_batch.put_cf(self.headers_cf(), key, b""); + total_rows_count += 1; + } + db_batch.put_cf(self.headers_cf(), TIP_KEY, batch.tip_row); + + let mut opts = rocksdb::WriteOptions::new(); + let bulk_import = self.bulk_import.load(Ordering::Relaxed); + opts.set_sync(!bulk_import); + opts.disable_wal(bulk_import); + self.db.write_opt(db_batch, &opts).unwrap(); + total_rows_count + } + + pub(crate) fn flush(&self) { + let mut config = self.get_config(); + for name in &self.cfs { + let cf = self.db.cf_handle(name).expect("missing CF"); + self.db.flush_cf(cf).expect("CF flush failed"); + } + if !config.compacted { + for name in &self.cfs { + info!("starting {} compaction", name); + let cf = self.db.cf_handle(name).expect("missing CF"); + self.db.compact_range_cf(cf, None::<&[u8]>, None::<&[u8]>); + } + config.compacted = true; + self.set_config(config); + info!("finished full compaction"); + self.start_compactions(); + } + } + + fn start_compactions(&self) { + self.bulk_import.store(false, Ordering::Relaxed); + for name in &self.cfs { + let cf = self.db.cf_handle(name).expect("missing CF"); + self.db + .set_options_cf(cf, &[("disable_auto_compactions", "false")]) + .expect("failed to start auto-compactions"); + } + debug!("auto-compactions enabled"); + } + + fn set_config(&self, config: Config) { + let mut opts = rocksdb::WriteOptions::default(); + opts.set_sync(true); + opts.disable_wal(false); + let value = serde_json::to_vec(&config).expect("failed to serialize config"); + self.db + .put_cf_opt(self.config_cf(), CONFIG_KEY, value, &opts) + .expect("DB::put failed"); + } + + fn get_config(&self) -> Config { + self.db + .get_cf(self.config_cf(), CONFIG_KEY) + .expect("DB::get failed") + .map(|value| serde_json::from_slice(&value).expect("failed to deserialize Config")) + .unwrap_or_else(|| Config { + compacted: false, + format: CURRENT_FORMAT, + }) + } +} + +pub(crate) struct ScanIterator<'a> { + prefix: Row, + iter: rocksdb::DBIterator<'a>, + done: bool, +} + +impl<'a> Iterator for ScanIterator<'a> { + type Item = Row; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + let (key, _) = self.iter.next()?; + if !key.starts_with(&self.prefix) { + self.done = true; + return None; + } + Some(key) + } +} + +impl Drop for DBStore { + fn drop(&mut self) { + info!("closing DB at {:?}", self.path); + } +} diff --git a/src/electrum.rs b/src/electrum.rs new file mode 100644 index 0000000..c29e58a --- /dev/null +++ b/src/electrum.rs @@ -0,0 +1,321 @@ +use anyhow::{bail, Context, Result}; +use bitcoin::{ + consensus::{deserialize, serialize}, + hashes::hex::{FromHex, ToHex}, + BlockHash, Txid, +}; +use rayon::prelude::*; +use serde_derive::{Deserialize, Serialize}; +use serde_json::{from_value, json, Value}; + +use std::collections::HashMap; +use std::iter::FromIterator; + +use crate::{ + cache::Cache, config::Config, daemon::Daemon, merkle::Proof, metrics::Histogram, + status::Status, tracker::Tracker, types::ScriptHash, +}; + +const ELECTRS_VERSION: &str = env!("CARGO_PKG_VERSION"); +const PROTOCOL_VERSION: &str = "1.4"; + +const UNKNOWN_FEE: isize = -1; // (allowed by Electrum protocol) + +/// Per-client Electrum protocol state +#[derive(Default)] +pub struct Client { + tip: Option, + status: HashMap, +} + +#[derive(Debug, Deserialize, Serialize)] +struct Request { + id: Value, + jsonrpc: String, + method: String, + + #[serde(default)] + params: Value, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +#[serde(untagged)] +enum Version { + Single(String), + Range(String, String), +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum TxGetArgs { + Txid((Txid,)), + TxidVerbose(Txid, bool), +} + +impl From for (Txid, bool) { + fn from(args: TxGetArgs) -> Self { + match args { + TxGetArgs::Txid((txid,)) => (txid, false), + TxGetArgs::TxidVerbose(txid, verbose) => (txid, verbose), + } + } +} + +/// Electrum RPC handler +pub struct Rpc { + tracker: Tracker, + cache: Cache, + rpc_duration: Histogram, + daemon: Daemon, + banner: String, +} + +impl Rpc { + pub fn new(config: &Config, tracker: Tracker) -> Result { + let rpc_duration = tracker.metrics().histogram_vec( + "rpc_duration", + "RPC duration (in seconds)", + &["method"], + ); + Ok(Self { + tracker, + cache: Cache::new(), + rpc_duration, + daemon: Daemon::connect(&config)?, + banner: config.server_banner.clone(), + }) + } + + pub fn sync(&mut self) -> Result<()> { + self.tracker.sync(&self.daemon) + } + + pub fn update_client(&self, client: &mut Client) -> Result> { + let chain = self.tracker.chain(); + let mut notifications = client + .status + .par_iter_mut() + .filter_map(|(scripthash, status)| -> Option> { + match self + .tracker + .update_status(status, &self.daemon, &self.cache) + { + Ok(true) => Some(Ok(notification( + "blockchain.scripthash.subscribe", + &[json!(scripthash), json!(status.statushash())], + ))), + Ok(false) => None, // statushash is the same + Err(e) => Some(Err(e)), + } + }) + .collect::>>() + .context("failed to update status")?; + + if let Some(old_tip) = client.tip { + let new_tip = self.tracker.chain().tip(); + if old_tip != new_tip { + client.tip = Some(new_tip); + let height = chain.height(); + let header = chain.get_block_header(height).unwrap(); + notifications.push(notification( + "blockchain.headers.subscribe", + &[json!({"hex": serialize(&header).to_hex(), "height": height})], + )); + } + } + Ok(notifications) + } + + pub fn handle_request(&self, client: &mut Client, value: Value) -> Result { + let Request { + id, + jsonrpc, + method, + params, + } = from_value(value).context("invalid request")?; + self.rpc_duration.observe_duration(&method, || { + let result = match method.as_str() { + "blockchain.scripthash.get_history" => { + self.scripthash_get_history(client, from_value(params)?) + } + "blockchain.scripthash.subscribe" => { + self.scripthash_subscribe(client, from_value(params)?) + } + "blockchain.transaction.broadcast" => { + self.transaction_broadcast(from_value(params)?) + } + "blockchain.transaction.get" => self.transaction_get(from_value(params)?), + "blockchain.transaction.get_merkle" => { + self.transaction_get_merkle(from_value(params)?) + } + "server.banner" => Ok(json!(self.banner)), + "server.donation_address" => Ok(Value::Null), + "server.peers.subscribe" => Ok(json!([])), + "blockchain.block.header" => self.block_header(from_value(params)?), + "blockchain.block.headers" => self.block_headers(from_value(params)?), + "blockchain.estimatefee" => self.estimate_fee(from_value(params)?), + "blockchain.headers.subscribe" => self.headers_subscribe(client), + "blockchain.relayfee" => self.relayfee(), + "mempool.get_fee_histogram" => self.get_fee_histogram(), + "server.ping" => Ok(Value::Null), + "server.version" => self.version(from_value(params)?), + &_ => bail!("unknown method '{}' with {}", method, params,), + }; + + Ok(match result { + Ok(value) => json!({"jsonrpc": jsonrpc, "id": id, "result": value}), + Err(err) => { + let msg = format!("RPC failed: {:#}", err); + warn!("{}", msg); + let error = json!({"code": 1, "message": msg}); + json!({"jsonrpc": jsonrpc, "id": id, "error": error}) + } + }) + }) + } + + fn headers_subscribe(&self, client: &mut Client) -> Result { + let chain = self.tracker.chain(); + client.tip = Some(chain.tip()); + let height = chain.height(); + let header = chain.get_block_header(height).unwrap(); + Ok(json!({"hex": serialize(header).to_hex(), "height": height})) + } + + fn block_header(&self, (height,): (usize,)) -> Result { + let chain = self.tracker.chain(); + let header = match chain.get_block_header(height) { + None => bail!("no header at {}", height), + Some(header) => header, + }; + Ok(json!(serialize(header).to_hex())) + } + + fn block_headers(&self, (start_height, count): (usize, usize)) -> Result { + let chain = self.tracker.chain(); + let max_count = 2016usize; + + let count = std::cmp::min( + std::cmp::min(count, max_count), + chain.height() - start_height + 1, + ); + let heights = start_height..(start_height + count); + let hex_headers = String::from_iter( + heights.map(|height| serialize(chain.get_block_header(height).unwrap()).to_hex()), + ); + + Ok(json!({"count": count, "hex": hex_headers, "max": max_count})) + } + + fn estimate_fee(&self, (nblocks,): (u16,)) -> Result { + Ok(self + .daemon + .estimate_fee(nblocks)? + .map(|fee_rate| json!(fee_rate.as_btc())) + .unwrap_or_else(|| json!(UNKNOWN_FEE))) + } + + fn relayfee(&self) -> Result { + Ok(json!(self.daemon.get_relay_fee()?.as_btc())) // [BTC/kB] + } + + fn scripthash_get_history( + &self, + client: &Client, + (scripthash,): (ScriptHash,), + ) -> Result { + let status = client + .status + .get(&scripthash) + .context("no subscription for scripthash")?; + Ok(json!(self + .tracker + .get_history(status) + .collect::>())) + } + + fn scripthash_subscribe( + &self, + client: &mut Client, + (scripthash,): (ScriptHash,), + ) -> Result { + let mut status = Status::new(scripthash); + self.tracker + .update_status(&mut status, &self.daemon, &self.cache)?; + let statushash = status.statushash(); + client.status.insert(scripthash, status); // skip if already exists + Ok(json!(statushash)) + } + + fn transaction_broadcast(&self, (tx_hex,): (String,)) -> Result { + let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?; + let tx = deserialize(&tx_bytes).context("invalid transaction")?; + let txid = self.daemon.broadcast(&tx)?; + Ok(json!(txid)) + } + + fn transaction_get(&self, args: TxGetArgs) -> Result { + let (txid, verbose) = args.into(); + if verbose { + let blockhash = self.tracker.get_blockhash_by_txid(txid); + return Ok(json!(self.daemon.get_transaction_info(&txid, blockhash)?)); + } + let cached = self.cache.get_tx(&txid, |tx| serialize(tx).to_hex()); + Ok(match cached { + Some(tx_hex) => json!(tx_hex), + None => { + debug!("tx cache miss: {}", txid); + let blockhash = self.tracker.get_blockhash_by_txid(txid); + json!(self.daemon.get_transaction_hex(&txid, blockhash)?) + } + }) + } + + fn transaction_get_merkle(&self, (txid, height): (Txid, usize)) -> Result { + let chain = self.tracker.chain(); + let blockhash = match chain.get_block_hash(height) { + None => bail!("missing block at {}", height), + Some(blockhash) => blockhash, + }; + let proof_to_value = |proof: &Proof| { + json!({ + "block_height": height, + "pos": proof.position(), + "merkle": proof.to_hex(), + }) + }; + if let Some(result) = self.cache.get_proof(blockhash, txid, proof_to_value) { + return Ok(result); + } + debug!("txids cache miss: {}", blockhash); + let txids = self.daemon.get_block_txids(blockhash)?; + match txids.iter().position(|current_txid| *current_txid == txid) { + None => bail!("missing tx {} for merkle proof", txid), + Some(position) => Ok(proof_to_value(&Proof::create(&txids, position))), + } + } + + fn get_fee_histogram(&self) -> Result { + Ok(json!(self.tracker.fees_histogram())) + } + + fn version(&self, (client_id, client_version): (String, Version)) -> Result { + match client_version { + Version::Single(v) if v == PROTOCOL_VERSION => (), + _ => { + bail!( + "{} requested {:?}, server supports {}", + client_id, + client_version, + PROTOCOL_VERSION + ); + } + }; + let server_id = format!("electrs/{}", ELECTRS_VERSION); + Ok(json!([server_id, PROTOCOL_VERSION])) + } +} + +fn notification(method: &str, params: &[Value]) -> Value { + json!({"jsonrpc": "2.0", "method": method, "params": params}) +} diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 0fc8d23..0000000 --- a/src/errors.rs +++ /dev/null @@ -1,17 +0,0 @@ -error_chain! { - types { - Error, ErrorKind, ResultExt, Result; - } - - errors { - Connection(msg: String) { - description("Connection error") - display("Connection error: {}", msg) - } - - Interrupt(sig: i32) { - description("Interruption by external signal") - display("Interrupted by signal {}", sig) - } - } -} diff --git a/src/fake.rs b/src/fake.rs deleted file mode 100644 index 4920352..0000000 --- a/src/fake.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::store::{ReadStore, Row, WriteStore}; -use crate::util::Bytes; - -pub struct FakeStore; - -impl ReadStore for FakeStore { - fn get(&self, _key: &[u8]) -> Option { - None - } - fn scan(&self, _prefix: &[u8]) -> Vec { - vec![] - } -} - -impl WriteStore for FakeStore { - fn write>(&self, _rows: I) {} - fn flush(&self) {} -} - -#[cfg(test)] -mod tests { - #[test] - fn test_fakestore() { - use crate::fake; - use crate::store::{ReadStore, Row, WriteStore}; - - let store = fake::FakeStore {}; - store.write(vec![Row { - key: b"k".to_vec(), - value: b"v".to_vec(), - }]); - store.flush(); - // nothing was actually written - assert!(store.get(b"").is_none()); - assert!(store.scan(b"").is_empty()); - } -} diff --git a/src/index.rs b/src/index.rs index 72d3847..66b47e4 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,436 +1,213 @@ -use bitcoin::blockdata::block::{Block, BlockHeader}; -use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut}; -use bitcoin::consensus::encode::{deserialize, serialize}; -use bitcoin::hash_types::{BlockHash, Txid}; -use crypto::digest::Digest; -use crypto::sha2::Sha256; -use std::collections::{HashMap, HashSet}; -use std::iter::FromIterator; -use std::sync::RwLock; +use anyhow::Result; +use bitcoin::consensus::{deserialize, serialize}; +use bitcoin::{Block, BlockHash, OutPoint, Txid}; -use crate::daemon::Daemon; -use crate::errors::*; -use crate::metrics::{ - Counter, Gauge, HistogramOpts, HistogramTimer, HistogramVec, MetricOpts, Metrics, -}; -use crate::signal::Waiter; -use crate::store::{ReadStore, Row, WriteStore}; -use crate::util::{ - full_hash, hash_prefix, spawn_thread, Bytes, FullHash, HashPrefix, HeaderEntry, HeaderList, - HeaderMap, SyncChannel, HASH_PREFIX_LEN, +use std::collections::HashMap; + +use crate::{ + chain::Chain, + daemon::Daemon, + db, + metrics::{Histogram, Metrics}, + types::{HeaderRow, ScriptHash, ScriptHashRow, SpendingPrefixRow, TxidRow}, }; -#[derive(Serialize, Deserialize)] -pub struct TxInKey { - pub code: u8, - pub prev_hash_prefix: HashPrefix, - pub prev_index: u16, -} - -#[derive(Serialize, Deserialize)] -pub struct TxInRow { - key: TxInKey, - pub txid_prefix: HashPrefix, -} - -impl TxInRow { - pub fn new(txid: &Txid, input: &TxIn) -> TxInRow { - TxInRow { - key: TxInKey { - code: b'I', - prev_hash_prefix: hash_prefix(&input.previous_output.txid[..]), - prev_index: input.previous_output.vout as u16, - }, - txid_prefix: hash_prefix(&txid[..]), - } - } - - pub fn filter(txid: &Txid, output_index: usize) -> Bytes { - bincode::serialize(&TxInKey { - code: b'I', - prev_hash_prefix: hash_prefix(&txid[..]), - prev_index: output_index as u16, - }) - .unwrap() - } - - pub fn to_row(&self) -> Row { - Row { - key: bincode::serialize(&self).unwrap(), - value: vec![], - } - } - - pub fn from_row(row: &Row) -> TxInRow { - bincode::deserialize(&row.key).expect("failed to parse TxInRow") - } -} - -#[derive(Serialize, Deserialize)] -pub struct TxOutKey { - code: u8, - script_hash_prefix: HashPrefix, -} - -#[derive(Serialize, Deserialize)] -pub struct TxOutRow { - key: TxOutKey, - pub txid_prefix: HashPrefix, -} - -impl TxOutRow { - pub fn new(txid: &Txid, output: &TxOut) -> TxOutRow { - TxOutRow { - key: TxOutKey { - code: b'O', - script_hash_prefix: hash_prefix(&compute_script_hash(&output.script_pubkey[..])), - }, - txid_prefix: hash_prefix(&txid[..]), - } - } - - pub fn filter(script_hash: &[u8]) -> Bytes { - bincode::serialize(&TxOutKey { - code: b'O', - script_hash_prefix: hash_prefix(&script_hash[..HASH_PREFIX_LEN]), - }) - .unwrap() - } - - pub fn to_row(&self) -> Row { - Row { - key: bincode::serialize(&self).unwrap(), - value: vec![], - } - } - - pub fn from_row(row: &Row) -> TxOutRow { - bincode::deserialize(&row.key).expect("failed to parse TxOutRow") - } -} - -#[derive(Serialize, Deserialize)] -pub struct TxKey { - code: u8, - pub txid: FullHash, -} - -pub struct TxRow { - pub key: TxKey, - pub height: u32, // value -} - -impl TxRow { - pub fn new(txid: &Txid, height: u32) -> TxRow { - TxRow { - key: TxKey { - code: b'T', - txid: full_hash(&txid[..]), - }, - height, - } - } - - pub fn filter_prefix(txid_prefix: HashPrefix) -> Bytes { - [b"T", &txid_prefix[..]].concat() - } - - pub fn filter_full(txid: &Txid) -> Bytes { - [b"T", &txid[..]].concat() - } - - pub fn to_row(&self) -> Row { - Row { - key: bincode::serialize(&self.key).unwrap(), - value: bincode::serialize(&self.height).unwrap(), - } - } - - pub fn from_row(row: &Row) -> TxRow { - TxRow { - key: bincode::deserialize(&row.key).expect("failed to parse TxKey"), - height: bincode::deserialize(&row.value).expect("failed to parse height"), - } - } -} - -#[derive(Serialize, Deserialize)] -struct BlockKey { - code: u8, - hash: FullHash, -} - -pub fn compute_script_hash(data: &[u8]) -> FullHash { - let mut hash = FullHash::default(); - let mut sha2 = Sha256::new(); - sha2.input(data); - sha2.result(&mut hash); - hash -} - -pub fn index_transaction<'a>( - txn: &'a Transaction, - height: usize, -) -> impl 'a + Iterator { - let null_hash = Txid::default(); - let txid = txn.txid(); - - let inputs = txn.input.iter().filter_map(move |input| { - if input.previous_output.txid == null_hash { - None - } else { - Some(TxInRow::new(&txid, &input).to_row()) - } - }); - let outputs = txn - .output - .iter() - .map(move |output| TxOutRow::new(&txid, &output).to_row()); - - // Persist transaction ID and confirmed height - inputs - .chain(outputs) - .chain(std::iter::once(TxRow::new(&txid, height as u32).to_row())) -} - -pub fn index_block<'a>(block: &'a Block, height: usize) -> impl 'a + Iterator { - let blockhash = block.block_hash(); - // Persist block hash and header - let row = Row { - key: bincode::serialize(&BlockKey { - code: b'B', - hash: full_hash(&blockhash[..]), - }) - .unwrap(), - value: serialize(&block.header), - }; - block - .txdata - .iter() - .flat_map(move |txn| index_transaction(&txn, height)) - .chain(std::iter::once(row)) -} - -pub fn last_indexed_block(blockhash: &BlockHash) -> Row { - // Store last indexed block (i.e. all previous blocks were indexed) - Row { - key: b"L".to_vec(), - value: serialize(blockhash), - } -} - -pub fn read_indexed_blockhashes(store: &dyn ReadStore) -> HashSet { - let mut result = HashSet::new(); - for row in store.scan(b"B") { - let key: BlockKey = bincode::deserialize(&row.key).unwrap(); - result.insert(deserialize(&key.hash).unwrap()); - } - result -} - -fn read_indexed_headers(store: &dyn ReadStore) -> HeaderList { - let latest_blockhash: BlockHash = match store.get(b"L") { - // latest blockheader persisted in the DB. - Some(row) => deserialize(&row).unwrap(), - None => BlockHash::default(), - }; - trace!("latest indexed blockhash: {}", latest_blockhash); - let mut map = HeaderMap::new(); - for row in store.scan(b"B") { - let key: BlockKey = bincode::deserialize(&row.key).unwrap(); - let header: BlockHeader = deserialize(&row.value).unwrap(); - map.insert(deserialize(&key.hash).unwrap(), header); - } - let mut headers = vec![]; - let null_hash = BlockHash::default(); - let mut blockhash = latest_blockhash; - while blockhash != null_hash { - let header = map - .remove(&blockhash) - .unwrap_or_else(|| panic!("missing {} header in DB", blockhash)); - blockhash = header.prev_blockhash; - headers.push(header); - } - headers.reverse(); - assert_eq!( - headers - .first() - .map(|h| h.prev_blockhash) - .unwrap_or(null_hash), - null_hash - ); - assert_eq!( - headers - .last() - .map(BlockHeader::block_hash) - .unwrap_or(null_hash), - latest_blockhash - ); - let mut result = HeaderList::empty(); - let entries = result.order(headers); - result.apply(entries, latest_blockhash); - result -} - +#[derive(Clone)] struct Stats { - blocks: Counter, - txns: Counter, - vsize: Counter, - height: Gauge, - duration: HistogramVec, + update_duration: Histogram, + update_size: Histogram, + lookup_duration: Histogram, } impl Stats { - fn new(metrics: &Metrics) -> Stats { - Stats { - blocks: metrics.counter(MetricOpts::new( - "electrs_index_blocks", - "# of indexed blocks", - )), - txns: metrics.counter(MetricOpts::new( - "electrs_index_txns", - "# of indexed transactions", - )), - vsize: metrics.counter(MetricOpts::new( - "electrs_index_vsize", - "# of indexed vbytes", - )), - height: metrics.gauge(MetricOpts::new( - "electrs_index_height", - "Last indexed block's height", - )), - duration: metrics.histogram_vec( - HistogramOpts::new("electrs_index_duration", "indexing duration (in seconds)"), + fn new(metrics: &Metrics) -> Self { + Self { + update_duration: metrics.histogram_vec( + "index_update_duration", + "Index update duration (in seconds)", + &["step"], + ), + update_size: metrics.histogram_vec( + "index_update_size", + "Index update size (in bytes)", + &["step"], + ), + lookup_duration: metrics.histogram_vec( + "index_lookup_duration", + "Index lookup duration (in seconds)", &["step"], ), } } - fn update(&self, block: &Block, height: usize) { - self.blocks.inc(); - self.txns.inc_by(block.txdata.len() as i64); - for tx in &block.txdata { - self.vsize.inc_by(tx.get_weight() as i64 / 4); - } - self.update_height(height); - } - - fn update_height(&self, height: usize) { - self.height.set(height as i64); - } - - fn start_timer(&self, step: &str) -> HistogramTimer { - self.duration.with_label_values(&[step]).start_timer() + fn report_stats(&self, batch: &db::WriteBatch) { + self.update_size + .observe_size("write_funding_rows", db_rows_size(&batch.funding_rows)); + self.update_size + .observe_size("write_spending_rows", db_rows_size(&batch.spending_rows)); + self.update_size + .observe_size("write_txid_rows", db_rows_size(&batch.txid_rows)); + self.update_size + .observe_size("write_header_rows", db_rows_size(&batch.header_rows)); + debug!( + "writing {} funding and {} spending rows from {} transactions, {} blocks", + batch.funding_rows.len(), + batch.spending_rows.len(), + batch.txid_rows.len(), + batch.header_rows.len() + ); } } +struct IndexResult { + header_row: HeaderRow, + funding_rows: Vec, + spending_rows: Vec, + txid_rows: Vec, +} + +impl IndexResult { + fn extend(&self, batch: &mut db::WriteBatch) { + let funding_rows = self.funding_rows.iter().map(ScriptHashRow::to_db_row); + let spending_rows = self.spending_rows.iter().map(SpendingPrefixRow::to_db_row); + let txid_rows = self.txid_rows.iter().map(TxidRow::to_db_row); + + batch.funding_rows.extend(funding_rows); + batch.spending_rows.extend(spending_rows); + batch.txid_rows.extend(txid_rows); + batch.header_rows.push(self.header_row.to_db_row()); + batch.tip_row = serialize(&self.header_row.header.block_hash()).into_boxed_slice(); + } +} + +/// Confirmed transactions' address index pub struct Index { - // TODO: store also latest snapshot. - headers: RwLock, - daemon: Daemon, + store: db::DBStore, + chain: Chain, stats: Stats, - batch_size: usize, } impl Index { - pub fn load( - store: &dyn ReadStore, - daemon: &Daemon, - metrics: &Metrics, - batch_size: usize, - ) -> Result { - let stats = Stats::new(metrics); - let headers = read_indexed_headers(store); - stats.height.set((headers.len() as i64) - 1); + pub(crate) fn load(store: db::DBStore, mut chain: Chain, metrics: &Metrics) -> Result { + if let Some(row) = store.get_tip() { + let tip = deserialize(&row).expect("invalid tip"); + let headers = store + .read_headers() + .into_iter() + .map(|row| HeaderRow::from_db_row(&row).header) + .collect(); + chain.load(headers, tip); + }; + Ok(Index { - headers: RwLock::new(headers), - daemon: daemon.reconnect()?, - stats, - batch_size, + store, + chain, + stats: Stats::new(metrics), }) } - pub fn reload(&self, store: &dyn ReadStore) { - let mut headers = self.headers.write().unwrap(); - *headers = read_indexed_headers(store); + pub(crate) fn chain(&self) -> &Chain { + &self.chain } - pub fn best_header(&self) -> Option { - let headers = self.headers.read().unwrap(); - headers.header_by_blockhash(&headers.tip()).cloned() + pub(crate) fn filter_by_txid<'a>(&'a self, txid: Txid) -> impl Iterator + 'a { + self.store + .iter_txid(TxidRow::scan_prefix(txid)) + .map(|row| TxidRow::from_db_row(&row).height()) + .filter_map(move |height| self.chain.get_block_hash(height)) } - pub fn get_header(&self, height: usize) -> Option { - self.headers - .read() - .unwrap() - .header_by_height(height) - .cloned() + pub(crate) fn filter_by_funding<'a>( + &'a self, + scripthash: ScriptHash, + ) -> impl Iterator + 'a { + self.store + .iter_funding(ScriptHashRow::scan_prefix(scripthash)) + .map(|row| ScriptHashRow::from_db_row(&row).height()) + .filter_map(move |height| self.chain.get_block_hash(height)) } - pub fn update(&self, store: &impl WriteStore, waiter: &Waiter) -> Result { - let daemon = self.daemon.reconnect()?; - let tip = daemon.getbestblockhash()?; - let new_headers: Vec = { - let indexed_headers = self.headers.read().unwrap(); - indexed_headers.order(daemon.get_new_headers(&indexed_headers, &tip)?) - }; - if let Some(latest_header) = new_headers.last() { - info!("{:?} ({} left to index)", latest_header, new_headers.len()); - }; - let height_map = HashMap::::from_iter( - new_headers.iter().map(|h| (*h.hash(), h.height())), - ); + pub(crate) fn filter_by_spending<'a>( + &'a self, + outpoint: OutPoint, + ) -> impl Iterator + 'a { + self.store + .iter_spending(SpendingPrefixRow::scan_prefix(outpoint)) + .map(|row| SpendingPrefixRow::from_db_row(&row).height()) + .filter_map(move |height| self.chain.get_block_hash(height)) + } - let chan = SyncChannel::new(1); - let sender = chan.sender(); - let blockhashes: Vec = new_headers.iter().map(|h| *h.hash()).collect(); - let batch_size = self.batch_size; - let fetcher = spawn_thread("fetcher", move || { - for chunk in blockhashes.chunks(batch_size) { - sender - .send(daemon.getblocks(&chunk)) - .expect("failed sending blocks to be indexed"); - } - sender - .send(Ok(vec![])) - .expect("failed sending explicit end of stream"); - }); + pub(crate) fn sync(&mut self, daemon: &Daemon, chunk_size: usize) -> Result<()> { loop { - waiter.poll()?; - let timer = self.stats.start_timer("fetch"); - let batch = chan - .receiver() - .recv() - .expect("block fetch exited prematurely")?; - timer.observe_duration(); - if batch.is_empty() { + let new_headers = daemon.get_new_headers(&self.chain)?; + if new_headers.is_empty() { break; } + info!( + "indexing {} blocks: [{}..{}]", + new_headers.len(), + new_headers.first().unwrap().height(), + new_headers.last().unwrap().height() + ); + for chunk in new_headers.chunks(chunk_size) { + let blockhashes: Vec = chunk.iter().map(|h| h.hash()).collect(); + let mut heights_map: HashMap = + chunk.iter().map(|h| (h.hash(), h.height())).collect(); - let rows_iter = batch.iter().flat_map(|block| { - let blockhash = block.block_hash(); - let height = *height_map - .get(&blockhash) - .unwrap_or_else(|| panic!("missing header for block {}", blockhash)); + let mut batch = db::WriteBatch::default(); - self.stats.update(block, height); // TODO: update stats after the block is indexed - index_block(block, height).chain(std::iter::once(last_indexed_block(&blockhash))) - }); - - let timer = self.stats.start_timer("index+write"); - store.write(rows_iter); - timer.observe_duration(); + daemon.for_blocks(blockhashes, |blockhash, block| { + let height = heights_map.remove(&blockhash).expect("unexpected block"); + let result = index_single_block(block, height); + result.extend(&mut batch); + })?; + assert!(heights_map.is_empty(), "some blocks were not indexed"); + batch.sort(); + self.stats.report_stats(&batch); + self.store.write(batch); + } + self.chain.update(new_headers); } - let timer = self.stats.start_timer("flush"); - store.flush(); // make sure no row is left behind - timer.observe_duration(); - - fetcher.join().expect("block fetcher failed"); - self.headers.write().unwrap().apply(new_headers, tip); - assert_eq!(tip, self.headers.read().unwrap().tip()); - self.stats - .update_height(self.headers.read().unwrap().len() - 1); - Ok(tip) + self.store.flush(); + Ok(()) + } +} + +fn db_rows_size(rows: &[db::Row]) -> usize { + rows.iter().map(|key| key.len()).sum() +} + +fn index_single_block(block: Block, height: usize) -> IndexResult { + let mut funding_rows = vec![]; + let mut spending_rows = vec![]; + let mut txid_rows = Vec::with_capacity(block.txdata.len()); + + for tx in &block.txdata { + txid_rows.push(TxidRow::new(tx.txid(), height)); + + funding_rows.extend( + tx.output + .iter() + .filter(|txo| !txo.script_pubkey.is_provably_unspendable()) + .map(|txo| { + let scripthash = ScriptHash::new(&txo.script_pubkey); + ScriptHashRow::new(scripthash, height) + }), + ); + + if tx.is_coin_base() { + continue; // coinbase doesn't have inputs + } + spending_rows.extend( + tx.input + .iter() + .map(|txin| SpendingPrefixRow::new(txin.previous_output, height)), + ); + } + IndexResult { + funding_rows, + spending_rows, + txid_rows, + header_row: HeaderRow::new(block.header), } } diff --git a/src/lib.rs b/src/lib.rs index 5e0b655..d0df5d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,28 +1,36 @@ -#![recursion_limit = "1024"] - #[macro_use] -extern crate error_chain; +extern crate anyhow; + #[macro_use] extern crate log; + #[macro_use] extern crate serde_derive; -#[macro_use] -extern crate serde_json; -// I really don't know why it fails without this line + extern crate configure_me; -pub mod app; -pub mod bulk; -pub mod cache; -pub mod config; -pub mod daemon; -pub mod errors; -pub mod fake; -pub mod index; -pub mod mempool; -pub mod metrics; -pub mod query; -pub mod rpc; -pub mod signal; -pub mod store; -pub mod util; +mod cache; +mod chain; +mod config; +mod daemon; +mod db; +mod electrum; +mod index; +mod mempool; +mod merkle; +mod metrics; +pub mod server; +mod signals; +mod status; +mod tracker; +mod types; + +pub use { + cache::Cache, + config::Config, + daemon::Daemon, + electrum::{Client, Rpc}, + status::Status, + tracker::Tracker, + types::ScriptHash, +}; diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..4d72a22 --- /dev/null +++ b/src/map.rs @@ -0,0 +1,77 @@ +use bitcoin::{BlockHash, BlockHeader}; + +use std::collections::HashMap; + +#[derive(Default)] +struct Chain { + by_height: Vec, +} + +impl Chain { + fn build(tip: BlockHash, by_hash: &HashMap) -> Self { + // verify full chain till genesis + let mut by_height = vec![]; + let mut blockhash = tip; + while blockhash != BlockHash::default() { + by_height.push(blockhash); + blockhash = match by_hash.get(&blockhash) { + Some(header) => header.prev_blockhash, + None => panic!("missing block header: {}", blockhash), + }; + } + by_height.reverse(); + Self { by_height } + } + + fn len(&self) -> usize { + self.by_height.len() + } + + fn tip(&self) -> Option<&BlockHash> { + self.by_height.last() + } +} + +#[derive(Default)] +pub struct BlockMap { + by_hash: HashMap, + chain: Chain, +} + +impl BlockMap { + pub(crate) fn new(headers: Vec) -> Self { + let mut map = Self::default(); + map.add_headers(headers); + map + } + + pub fn chain(&self) -> &[BlockHash] { + &self.chain.by_height + } + + /// May return stale headers + pub fn get_header(&self, hash: &BlockHash) -> Option<&BlockHeader> { + self.by_hash.get(hash) + } + + fn add_headers(&mut self, headers: Vec) { + let total_blocks = headers.len(); + let mut new_blocks = 0usize; + for header in headers { + let hash = header.block_hash(); + self.by_hash.entry(hash).or_insert_with(|| { + new_blocks += 1; + header + }); + } + debug!("added {}/{} headers", new_blocks, total_blocks,); + } + + pub fn update(&mut self, tip: BlockHash, headers: Vec) { + self.add_headers(headers); + let chain = Chain::build(tip, &self.by_hash); + assert_eq!(chain.tip(), Some(&tip)); + info!("verified {} headers, tip={}", chain.len(), tip); + self.chain = chain; + } +} diff --git a/src/mempool.rs b/src/mempool.rs index 99b329b..6f82f2d 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -1,287 +1,199 @@ -use bitcoin::blockdata::transaction::Transaction; -use bitcoin::hash_types::Txid; -use std::collections::{BTreeMap, HashMap, HashSet}; +use anyhow::Result; + +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::convert::TryInto; use std::iter::FromIterator; use std::ops::Bound; -use std::sync::Mutex; -use crate::daemon::{Daemon, MempoolEntry}; -use crate::errors::*; -use crate::index::index_transaction; -use crate::metrics::{ - Gauge, GaugeVec, HistogramOpts, HistogramTimer, HistogramVec, MetricOpts, Metrics, -}; -use crate::store::{ReadStore, Row}; -use crate::util::Bytes; +use bitcoin::hashes::Hash; +use bitcoin::{Amount, OutPoint, Transaction, Txid}; +use bitcoincore_rpc::json; +use rayon::prelude::*; +use serde::ser::{Serialize, SerializeSeq, Serializer}; -const VSIZE_BIN_WIDTH: u32 = 100_000; // in vbytes +use crate::{daemon::Daemon, types::ScriptHash}; -struct MempoolStore { - map: BTreeMap>, +pub(crate) struct Entry { + pub txid: Txid, + pub tx: Transaction, + pub fee: Amount, + pub vsize: u64, + pub has_unconfirmed_inputs: bool, } -impl MempoolStore { - fn new() -> MempoolStore { - MempoolStore { - map: BTreeMap::new(), - } - } +/// Mempool current state +pub(crate) struct Mempool { + entries: HashMap, + by_funding: BTreeSet<(ScriptHash, Txid)>, + by_spending: BTreeSet<(OutPoint, Txid)>, + histogram: Histogram, - fn add(&mut self, tx: &Transaction) { - let rows = index_transaction(tx, 0); - for row in rows { - let (key, value) = row.into_pair(); - self.map.entry(key).or_insert_with(Vec::new).push(value); - } - } - - fn remove(&mut self, tx: &Transaction) { - let rows = index_transaction(tx, 0); - for row in rows { - let (key, value) = row.into_pair(); - let no_values_left = { - let values = self - .map - .get_mut(&key) - .unwrap_or_else(|| panic!("missing key {} in mempool", hex::encode(&key))); - let last_value = values - .pop() - .unwrap_or_else(|| panic!("no values found for key {}", hex::encode(&key))); - // TxInRow and TxOutRow have an empty value, TxRow has height=0 as value. - assert_eq!( - value, - last_value, - "wrong value for key {}: {}", - hex::encode(&key), - hex::encode(&last_value) - ); - values.is_empty() - }; - if no_values_left { - self.map.remove(&key).unwrap(); - } - } - } + txid_min: Txid, + txid_max: Txid, } -impl ReadStore for MempoolStore { - fn get(&self, key: &[u8]) -> Option { - Some(self.map.get(key)?.last()?.to_vec()) - } - fn scan(&self, prefix: &[u8]) -> Vec { - let range = self - .map - .range((Bound::Included(prefix.to_vec()), Bound::Unbounded)); - let mut rows = vec![]; - for (key, values) in range { - if !key.starts_with(prefix) { - break; - } - if let Some(value) = values.last() { - rows.push(Row { - key: key.to_vec(), - value: value.to_vec(), - }); - } - } - rows - } -} +impl Mempool { + pub fn new() -> Self { + Self { + entries: Default::default(), + by_funding: Default::default(), + by_spending: Default::default(), + histogram: Histogram::empty(), -struct Item { - tx: Transaction, // stored for faster retrieval and index removal - entry: MempoolEntry, // caches mempool fee rates -} - -struct Stats { - count: Gauge, - update: HistogramVec, - vsize: GaugeVec, - max_fee_rate: Mutex, -} - -impl Stats { - fn start_timer(&self, step: &str) -> HistogramTimer { - self.update.with_label_values(&[step]).start_timer() - } - - fn update(&self, entries: &[&MempoolEntry]) { - let mut bands: Vec<(f32, u32)> = vec![]; - let mut fee_rate = 1.0f32; // [sat/vbyte] - let mut vsize = 0u32; // vsize of transactions paying <= fee_rate - for e in entries { - while fee_rate < e.fee_per_vbyte() { - bands.push((fee_rate, vsize)); - fee_rate *= 2.0; - } - vsize += e.vsize(); - } - let mut max_fee_rate = self.max_fee_rate.lock().unwrap(); - loop { - bands.push((fee_rate, vsize)); - if fee_rate < *max_fee_rate { - fee_rate *= 2.0; - continue; - } - *max_fee_rate = fee_rate; - break; - } - drop(max_fee_rate); - for (fee_rate, vsize) in bands { - // labels should be ordered by fee_rate value - let label = format!("≤{:10.0}", fee_rate); - self.vsize - .with_label_values(&[&label]) - .set(f64::from(vsize)); - } - } -} - -pub struct Tracker { - items: HashMap, - index: MempoolStore, - histogram: Vec<(f32, u32)>, - stats: Stats, -} - -impl Tracker { - pub fn new(metrics: &Metrics) -> Tracker { - Tracker { - items: HashMap::new(), - index: MempoolStore::new(), - histogram: vec![], - stats: Stats { - count: metrics.gauge(MetricOpts::new( - "electrs_mempool_count", - "# of mempool transactions", - )), - update: metrics.histogram_vec( - HistogramOpts::new( - "electrs_mempool_update", - "Time to update mempool (in seconds)", - ), - &["step"], - ), - vsize: metrics.gauge_vec( - MetricOpts::new( - "electrs_mempool_vsize", - "Total vsize of transactions paying at most given fee rate", - ), - &["fee_rate"], - ), - max_fee_rate: Mutex::new(1.0), - }, + txid_min: Txid::from_inner([0x00; 32]), + txid_max: Txid::from_inner([0xFF; 32]), } } - pub fn has_txn(&self, txid: &Txid) -> bool { - self.items.contains_key(txid) - } - - pub fn get_fee(&self, txid: &Txid) -> Option { - self.items.get(txid).map(|stats| stats.entry.fee()) - } - - /// Returns vector of (fee_rate, vsize) pairs, where fee_{n-1} > fee_n and vsize_n is the - /// total virtual size of mempool transactions with fee in the bin [fee_{n-1}, fee_n]. - /// Note: fee_{-1} is implied to be infinite. - pub fn fee_histogram(&self) -> &Vec<(f32, u32)> { + pub(crate) fn fees_histogram(&self) -> &Histogram { &self.histogram } - pub fn index(&self) -> &dyn ReadStore { - &self.index + pub(crate) fn get(&self, txid: &Txid) -> Option<&Entry> { + self.entries.get(txid) } - pub fn update(&mut self, daemon: &Daemon) -> Result<()> { - let timer = self.stats.start_timer("fetch"); - let new_txids = daemon - .getmempooltxids() - .chain_err(|| "failed to update mempool from daemon")?; - let old_txids = HashSet::from_iter(self.items.keys().cloned()); - timer.observe_duration(); + pub(crate) fn filter_by_funding(&self, scripthash: &ScriptHash) -> Vec<&Entry> { + let range = ( + Bound::Included((*scripthash, self.txid_min)), + Bound::Included((*scripthash, self.txid_max)), + ); + self.by_funding + .range(range) + .map(|(_, txid)| self.get(txid).expect("missing funding mempool tx")) + .collect() + } - let timer = self.stats.start_timer("add"); - let txids_iter = new_txids.difference(&old_txids); - let entries: Vec<(&Txid, MempoolEntry)> = txids_iter + pub(crate) fn filter_by_spending(&self, outpoint: &OutPoint) -> Vec<&Entry> { + let range = ( + Bound::Included((*outpoint, self.txid_min)), + Bound::Included((*outpoint, self.txid_max)), + ); + self.by_spending + .range(range) + .map(|(_, txid)| self.get(txid).expect("missing spending mempool tx")) + .collect() + } + + pub fn sync(&mut self, daemon: &Daemon) -> Result<()> { + let txids = daemon.get_mempool_txids()?; + debug!("loading {} mempool transactions", txids.len()); + + let new_txids = HashSet::::from_iter(txids); + let old_txids = HashSet::::from_iter(self.entries.keys().copied()); + + let to_add = &new_txids - &old_txids; + let to_remove = &old_txids - &new_txids; + + let removed = to_remove.len(); + for txid in to_remove { + self.remove_entry(txid); + } + let entries: Vec<_> = to_add + .par_iter() .filter_map(|txid| { - match daemon.getmempoolentry(txid) { - Ok(entry) => Some((txid, entry)), - Err(err) => { - debug!("no mempool entry {}: {}", txid, err); // e.g. new block or RBF - None // ignore this transaction for now - } + match ( + daemon.get_transaction(txid, None), + daemon.get_mempool_entry(txid), + ) { + (Ok(tx), Ok(entry)) => Some((txid, tx, entry)), + _ => None, } }) .collect(); - if !entries.is_empty() { - let txids: Vec<&Txid> = entries.iter().map(|(txid, _)| *txid).collect(); - let txs = match daemon.gettransactions(&txids) { - Ok(txs) => txs, - Err(err) => { - debug!("failed to get transactions {:?}: {}", txids, err); // e.g. new block or RBF - return Ok(()); // keep the mempool until next update() - } - }; - for ((txid, entry), tx) in entries.into_iter().zip(txs.into_iter()) { - assert_eq!(tx.txid(), *txid); - self.add(txid, tx, entry); - } + let added = entries.len(); + for (txid, tx, entry) in entries { + self.add_entry(*txid, tx, entry); } - timer.observe_duration(); - - let timer = self.stats.start_timer("remove"); - for txid in old_txids.difference(&new_txids) { - self.remove(txid); - } - timer.observe_duration(); - - let timer = self.stats.start_timer("fees"); - self.update_fee_histogram(); - timer.observe_duration(); - - self.stats.count.set(self.items.len() as i64); + self.histogram = Histogram::new(self.entries.values().map(|e| (e.fee, e.vsize))); + debug!( + "{} mempool txs: {} added, {} removed", + self.entries.len(), + added, + removed, + ); Ok(()) } - fn add(&mut self, txid: &Txid, tx: Transaction, entry: MempoolEntry) { - self.index.add(&tx); - self.items.insert(*txid, Item { tx, entry }); + fn add_entry(&mut self, txid: Txid, tx: Transaction, entry: json::GetMempoolEntryResult) { + for txi in &tx.input { + self.by_spending.insert((txi.previous_output, txid)); + } + for txo in &tx.output { + let scripthash = ScriptHash::new(&txo.script_pubkey); + self.by_funding.insert((scripthash, txid)); // may have duplicates + } + let entry = Entry { + txid, + tx, + vsize: entry.vsize, + fee: entry.fees.base, + has_unconfirmed_inputs: !entry.depends.is_empty(), + }; + assert!( + self.entries.insert(txid, entry).is_none(), + "duplicate mempool txid" + ); } - fn remove(&mut self, txid: &Txid) { - let stats = self - .items - .remove(txid) - .unwrap_or_else(|| panic!("missing mempool tx {}", txid)); - self.index.remove(&stats.tx); - } - - fn update_fee_histogram(&mut self) { - let mut entries: Vec<&MempoolEntry> = self.items.values().map(|stat| &stat.entry).collect(); - entries.sort_unstable_by(|e1, e2| { - e1.fee_per_vbyte().partial_cmp(&e2.fee_per_vbyte()).unwrap() - }); - self.histogram = electrum_fees(&entries); - self.stats.update(&entries); - } -} - -fn electrum_fees(entries: &[&MempoolEntry]) -> Vec<(f32, u32)> { - let mut histogram = vec![]; - let mut bin_size = 0; - let mut last_fee_rate = None; - for e in entries.iter().rev() { - last_fee_rate = Some(e.fee_per_vbyte()); - bin_size += e.vsize(); - if bin_size > VSIZE_BIN_WIDTH { - // vsize of transactions paying >= e.fee_per_vbyte() - histogram.push((e.fee_per_vbyte(), bin_size)); - bin_size = 0; + fn remove_entry(&mut self, txid: Txid) { + let entry = self.entries.remove(&txid).expect("missing tx from mempool"); + for txi in entry.tx.input { + self.by_spending.remove(&(txi.previous_output, txid)); + } + for txo in entry.tx.output { + let scripthash = ScriptHash::new(&txo.script_pubkey); + self.by_funding.remove(&(scripthash, txid)); // may have misses } } - if let Some(fee_rate) = last_fee_rate { - histogram.push((fee_rate, bin_size)); - } - histogram +} + +pub(crate) struct Histogram { + /// bins[64-i] contains the total vsize of transactions with fee rate [2**(i-1), 2**i). + /// bins[63] = [1, 2) + /// bins[62] = [2, 4) + /// bins[61] = [4, 8) + /// bins[60] = [8, 16) + /// ... + /// bins[1] = [2**62, 2**63) + /// bins[0] = [2**63, 2**64) + bins: [u64; Histogram::SIZE], +} + +impl Histogram { + const SIZE: usize = 64; + + fn empty() -> Self { + Self::new(std::iter::empty()) + } + + fn new(items: impl Iterator) -> Self { + let mut bins = [0; Self::SIZE]; + for (fee, vsize) in items { + let fee_rate = fee.as_sat() / vsize; + let index: usize = fee_rate.leading_zeros().try_into().unwrap(); + // skip transactions with too low fee rate (<1 sat/vB) + if let Some(bin) = bins.get_mut(index) { + *bin += vsize + } + } + Self { bins } + } +} + +impl Serialize for Histogram { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.bins.len()))?; + // https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#mempool-get-fee-histogram + let mut fee_rate = std::u64::MAX; + for vsize in self.bins.iter() { + let element = (fee_rate, *vsize); + seq.serialize_element(&element)?; + fee_rate >>= 1; + } + seq.end() + } } diff --git a/src/merkle.rs b/src/merkle.rs new file mode 100644 index 0000000..65086b9 --- /dev/null +++ b/src/merkle.rs @@ -0,0 +1,79 @@ +use bitcoin::{ + hashes::{hex::ToHex, Hash}, + TxMerkleNode, Txid, +}; + +pub(crate) struct Proof { + proof: Vec, + position: usize, +} + +impl Proof { + pub(crate) fn create(txids: &[Txid], position: usize) -> Self { + assert!(position < txids.len()); + let mut offset = position; + let mut hashes: Vec = txids + .iter() + .map(|txid| TxMerkleNode::from_hash(txid.as_hash())) + .collect(); + + let mut proof = vec![]; + while hashes.len() > 1 { + if hashes.len() % 2 != 0 { + let last = *hashes.last().unwrap(); + hashes.push(last); + } + offset = if offset % 2 == 0 { + offset + 1 + } else { + offset - 1 + }; + proof.push(hashes[offset]); + offset /= 2; + hashes = hashes + .chunks(2) + .map(|pair| { + let left = pair[0]; + let right = pair[1]; + let input = [&left[..], &right[..]].concat(); + TxMerkleNode::hash(&input) + }) + .collect() + } + Self { proof, position } + } + + pub(crate) fn to_hex(&self) -> Vec { + self.proof.iter().map(|node| node.to_hex()).collect() + } + + pub(crate) fn position(&self) -> usize { + self.position + } +} + +// TODO: add tests + +// // {"id":37,"jsonrpc":"2.0", +// "result":{"block_height":333961,"merkle":[ +// "5d8cfb001d9ec17861ad9c158244239cb6e3298a619b2a5f7b176ddd54459c75", +// "06811172e13312f2e496259d2c8a7262f1192be5223fcf4d6a9ed7f58a2175ba", +// "cbcec841dea3294706809d1510c72b4424d141fac89106af65b70399b1d79f3f", +// "a24d6c3601a54d40f4350e6c8887bf82a873fe8619f95c772b573ec0373119d3", +// "2015c1bb133ee2c972e55fdcd205a9aee7b0122fd74c2f5d5d27b24a562c7790", +// "f379496fef2e603c4e1c03e2179ebaf5153d6463b8d61aa16d41db3321a18165", +// "7a798d6529663fd472d26cc90c434b64f78955747ac2f93c8dcd35b8f684946e", +// "ad3811062b8db664f2342cbff1b491865310b74416dd7b901f14d980886821f8"],"pos":157}} +// // {"id":38,"jsonrpc":"2.0", +// "result":{"block_height":437303,"merkle":[ +// "d29769df672657689fd6d293b416ee9211c77fbe243ab7820813f327b0e8dd47", +// "d71f0947b47cab0f64948acfe52d41c293f492fe9627690c330d4004f2852ce4", +// "5f36c4330c727d7c8d98cc906cb286f13a61b5b4cab2124c5d041897834b42d8", +// "e77d181f83355ed38d0e6305fdb87c9637373fd90d1dfb911262ac55d260181e", +// "a8f83ca44dc486d9d45c4cff9567839c254bda96e6960d310a5e471c70c6a95b", +// "e9a5ff7f74cb060b451ed2cd27de038efff4df911f4e0f99e2661b46ebcc7e1c", +// "6b0144095e3f0e0d0551cbaa6c5dfc89387024f836528281b6d290e356e196cf", +// "bb0761b0636ffd387e0ce322289a3579e926b6813e090130a88228bd80cff982", +// "ac327124304cccf6739da308a25bb365a6b63e9344bad2be139b0b02c042567c", +// "42e11f2d67050cd31295f85507ebc7706fc4c1fddf1e5a45b98ae3f7c63d2592", +// "52657042fcfc88067524bf6c5f9a66414c7de4f4fcabcb65bca56fa84cf309b4"],"pos":6}} diff --git a/src/metrics.rs b/src/metrics.rs index b2c98cc..3879406 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -1,162 +1,87 @@ -use prometheus::{self, Encoder, IntGauge}; -use std::fs; -use std::io; -use std::net::SocketAddr; -use std::thread; -use std::time::Duration; - -pub use prometheus::{ - GaugeVec, Histogram, HistogramOpts, HistogramTimer, HistogramVec, IntCounter as Counter, - IntCounterVec as CounterVec, IntGauge as Gauge, Opts as MetricOpts, +use anyhow::{Context, Result}; +use hyper::server::{Handler, Listening, Request, Response, Server}; +use prometheus::{ + self, process_collector::ProcessCollector, Encoder, HistogramOpts, HistogramVec, Registry, }; -use crate::errors::*; -use crate::util::spawn_thread; +use std::net::SocketAddr; pub struct Metrics { - reg: prometheus::Registry, - addr: SocketAddr, + reg: Registry, + listen: Listening, +} + +impl Drop for Metrics { + fn drop(&mut self) { + debug!("closing Prometheus server"); + if let Err(e) = self.listen.close() { + warn!("failed to stop Prometheus server: {}", e); + } + } +} + +#[derive(Clone)] +pub struct Histogram { + hist: HistogramVec, +} + +impl Histogram { + pub fn observe_size(&self, label: &str, value: usize) { + self.hist.with_label_values(&[label]).observe(value as f64); + } + + pub fn observe_duration(&self, label: &str, func: F) -> T + where + F: FnOnce() -> T, + { + self.hist + .with_label_values(&[label]) + .observe_closure_duration(func) + } +} + +struct RegistryHandler { + reg: Registry, +} + +impl RegistryHandler { + fn gather(&self) -> Result> { + let mut buffer = vec![]; + prometheus::TextEncoder::new() + .encode(&self.reg.gather(), &mut buffer) + .context("failed to encode metrics")?; + Ok(buffer) + } +} + +impl Handler for RegistryHandler { + fn handle(&self, req: Request, res: Response) { + trace!("{} {}", req.method, req.uri); + let buffer = self.gather().expect("failed to gather metrics"); + res.send(&buffer).expect("send failed"); + } } impl Metrics { - pub fn new(addr: SocketAddr) -> Metrics { - Metrics { - reg: prometheus::Registry::new(), - addr, - } + pub fn new(addr: SocketAddr) -> Result { + let reg = Registry::new(); + + reg.register(Box::new(ProcessCollector::for_self())) + .expect("failed to register ProcessCollector"); + + let listen = Server::http(addr)? + .handle(RegistryHandler { reg: reg.clone() }) + .with_context(|| format!("failed to serve on {}", addr))?; + info!("serving Prometheus metrics on {}", addr); + Ok(Self { reg, listen }) } - pub fn counter(&self, opts: prometheus::Opts) -> Counter { - let c = Counter::with_opts(opts).unwrap(); - self.reg.register(Box::new(c.clone())).unwrap(); - c - } - - pub fn counter_vec(&self, opts: prometheus::Opts, labels: &[&str]) -> CounterVec { - let c = CounterVec::new(opts, labels).unwrap(); - self.reg.register(Box::new(c.clone())).unwrap(); - c - } - - pub fn gauge(&self, opts: prometheus::Opts) -> Gauge { - let g = Gauge::with_opts(opts).unwrap(); - self.reg.register(Box::new(g.clone())).unwrap(); - g - } - - pub fn gauge_vec(&self, opts: prometheus::Opts, labels: &[&str]) -> GaugeVec { - let g = GaugeVec::new(opts, labels).unwrap(); - self.reg.register(Box::new(g.clone())).unwrap(); - g - } - - pub fn gauge_int(&self, opts: prometheus::Opts) -> IntGauge { - let g = Gauge::with_opts(opts).unwrap(); - self.reg.register(Box::new(g.clone())).unwrap(); - g - } - - pub fn histogram(&self, opts: prometheus::HistogramOpts) -> Histogram { - let h = Histogram::with_opts(opts).unwrap(); - self.reg.register(Box::new(h.clone())).unwrap(); - h - } - - pub fn histogram_vec(&self, opts: prometheus::HistogramOpts, labels: &[&str]) -> HistogramVec { - let h = HistogramVec::new(opts, labels).unwrap(); - self.reg.register(Box::new(h.clone())).unwrap(); - h - } - - pub fn start(&self) { - let server = tiny_http::Server::http(self.addr).unwrap_or_else(|e| { - panic!( - "failed to start monitoring HTTP server at {}: {}", - self.addr, e - ) - }); - start_process_exporter(&self); - let reg = self.reg.clone(); - spawn_thread("metrics", move || loop { - if let Err(e) = handle_request(®, server.recv()) { - error!("http error: {}", e); - } - }); + pub fn histogram_vec(&self, name: &str, desc: &str, labels: &[&str]) -> Histogram { + let opts = HistogramOpts::new(name, desc); + let hist = HistogramVec::new(opts, labels).unwrap(); + self.reg + .register(Box::new(hist.clone())) + .expect("failed to register Histogram"); + Histogram { hist } } } - -fn handle_request( - reg: &prometheus::Registry, - request: io::Result, -) -> io::Result<()> { - let request = request?; - let mut buffer = vec![]; - prometheus::TextEncoder::new() - .encode(®.gather(), &mut buffer) - .unwrap(); - let response = tiny_http::Response::from_data(buffer); - request.respond(response) -} - -struct Stats { - utime: f64, - rss: u64, - fds: usize, -} - -fn parse_stats() -> Result { - let value = - fs::read_to_string("/proc/self/stat").chain_err(|| "failed to read /proc/self/stat")?; - let parts: Vec<&str> = value.split_whitespace().collect(); - let page_size = page_size::get() as u64; - let ticks_per_second = sysconf::raw::sysconf(sysconf::raw::SysconfVariable::ScClkTck) - .expect("failed to get _SC_CLK_TCK") as f64; - - let parse_part = |index: usize, name: &str| -> Result { - Ok(parts - .get(index) - .chain_err(|| format!("missing {}: {:?}", name, parts))? - .parse::() - .chain_err(|| format!("invalid {}: {:?}", name, parts))?) - }; - - // For details, see '/proc/[pid]/stat' section at `man 5 proc`: - let utime = parse_part(13, "utime")? as f64 / ticks_per_second; - let rss = parse_part(23, "rss")? * page_size; - let fds = fs::read_dir("/proc/self/fd") - .chain_err(|| "failed to read /proc/self/fd directory")? - .count(); - Ok(Stats { utime, rss, fds }) -} - -fn start_process_exporter(metrics: &Metrics) { - let rss = metrics.gauge(MetricOpts::new( - "electrs_process_memory_rss", - "Resident memory size [bytes]", - )); - let cpu = metrics.gauge_vec( - MetricOpts::new( - "electrs_process_cpu_usage", - "CPU usage by this process [seconds]", - ), - &["type"], - ); - let fds = metrics.gauge(MetricOpts::new( - "electrs_process_open_fds", - "# of file descriptors", - )); - spawn_thread("exporter", move || loop { - match parse_stats() { - Ok(stats) => { - cpu.with_label_values(&["utime"]).set(stats.utime as f64); - rss.set(stats.rss as i64); - fds.set(stats.fds as i64); - } - Err(e) => { - warn!("failed to export process stats: {}", e); - return; - } - } - thread::sleep(Duration::from_secs(5)); - }); -} diff --git a/src/notify.rs b/src/notify.rs deleted file mode 100644 index e695dfc..0000000 --- a/src/notify.rs +++ /dev/null @@ -1,70 +0,0 @@ -// TODO: network::socket::Socket needs to be reimplemented. - -use bitcoin::network::constants::Network; -use bitcoin::network::message::NetworkMessage; -use bitcoin::network::message_blockdata::InvType; -use bitcoin::network::socket::Socket; -use bitcoin::hash_types::Txid; -use bitcoin::util::Error; - -use std::sync::mpsc::Sender; -use std::thread; -use std::time::Duration; - -use crate::util; - -fn connect() -> Result { - let mut sock = Socket::new(Network::Bitcoin); - sock.connect("127.0.0.1", 8333)?; - Ok(sock) -} - -fn handle(mut sock: Socket, tx: Sender) { - let mut outgoing = vec![sock.version_message(0).unwrap()]; - loop { - for msg in outgoing.split_off(0) { - trace!("send {:?}", msg); - if let Err(e) = sock.send_message(msg.clone()) { - warn!("failed to connect to node: {}", e); - break; - } - } - // Receive new message - let msg = match sock.receive_message() { - Ok(msg) => msg, - Err(e) => { - warn!("failed to receive p2p message: {}", e); - break; - } - }; - trace!("recv {:?}", msg); - match msg { - NetworkMessage::Alert(_) => continue, // deprecated - NetworkMessage::Version(_) => outgoing.push(NetworkMessage::Verack), - NetworkMessage::Ping(nonce) => outgoing.push(NetworkMessage::Pong(nonce)), - NetworkMessage::Inv(ref inventory) => { - inventory - .iter() - .filter(|inv| inv.inv_type == InvType::Block) - .for_each(|inv| tx.send(inv.hash).expect("failed to send message")); - } - _ => (), - }; - } -} - -pub fn run() -> util::Channel { - let chan = util::Channel::new(); - let tx = chan.sender(); - - util::spawn_thread("p2p", move || loop { - // TODO: support testnet and regtest as well. - match connect() { - Ok(sock) => handle(sock, tx.clone()), - Err(e) => warn!("p2p error: {}", e), - } - thread::sleep(Duration::from_secs(3)); - }); - - chan -} diff --git a/src/query.rs b/src/query.rs deleted file mode 100644 index 715aeab..0000000 --- a/src/query.rs +++ /dev/null @@ -1,580 +0,0 @@ -use bitcoin::blockdata::transaction::Transaction; -use bitcoin::consensus::encode::deserialize; -use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid}; -use bitcoin::hashes::sha256d::Hash as Sha256dHash; -use bitcoin::hashes::hex::ToHex; -use bitcoin::hashes::Hash; -use crypto::digest::Digest; -use crypto::sha2::Sha256; -use serde_json::Value; -use std::collections::HashMap; -use std::sync::{Arc, RwLock}; - -use crate::app::App; -use crate::cache::TransactionCache; -use crate::errors::*; -use crate::index::{compute_script_hash, TxInRow, TxOutRow, TxRow}; -use crate::mempool::Tracker; -use crate::metrics::{HistogramOpts, HistogramVec, Metrics}; -use crate::store::{ReadStore, Row}; -use crate::util::{FullHash, HashPrefix, HeaderEntry}; - -pub struct FundingOutput { - pub txn_id: Txid, - pub height: u32, - pub output_index: usize, - pub value: u64, -} - -type OutPoint = (Txid, usize); // (txid, output_index) - -struct SpendingInput { - txn_id: Txid, - height: u32, - funding_output: OutPoint, - value: u64, -} - -pub struct Status { - confirmed: (Vec, Vec), - mempool: (Vec, Vec), - txn_fees: HashMap, -} - -fn calc_balance((funding, spending): &(Vec, Vec)) -> i64 { - let funded: u64 = funding.iter().map(|output| output.value).sum(); - let spent: u64 = spending.iter().map(|input| input.value).sum(); - funded as i64 - spent as i64 -} - -pub struct HistoryItem { - height: i32, - tx_hash: Txid, - fee: Option, // need to be set only for unconfirmed transactions (i.e. height <= 0) -} - -impl HistoryItem { - pub fn to_json(&self) -> Value { - let mut result = json!({ "height": self.height, "tx_hash": self.tx_hash.to_hex()}); - self.fee.map(|f| { - result - .as_object_mut() - .unwrap() - .insert("fee".to_string(), json!(f)) - }); - result - } -} - -impl Status { - fn funding(&self) -> impl Iterator { - self.confirmed.0.iter().chain(self.mempool.0.iter()) - } - - fn spending(&self) -> impl Iterator { - self.confirmed.1.iter().chain(self.mempool.1.iter()) - } - - pub fn confirmed_balance(&self) -> i64 { - calc_balance(&self.confirmed) - } - - pub fn mempool_balance(&self) -> i64 { - calc_balance(&self.mempool) - } - - pub fn history(&self) -> Vec { - let mut txns_map = HashMap::::new(); - for f in self.funding() { - txns_map.insert(f.txn_id, f.height as i32); - } - for s in self.spending() { - txns_map.insert(s.txn_id, s.height as i32); - } - let mut items: Vec = txns_map - .into_iter() - .map(|item| HistoryItem { - height: item.1, - tx_hash: item.0, - fee: self.txn_fees.get(&item.0).cloned(), - }) - .collect(); - items.sort_unstable_by_key(|item| item.height); - items - } - - pub fn unspent(&self) -> Vec<&FundingOutput> { - let mut outputs_map = HashMap::::new(); - for f in self.funding() { - outputs_map.insert((f.txn_id, f.output_index), f); - } - for s in self.spending() { - if outputs_map.remove(&s.funding_output).is_none() { - warn!("failed to remove {:?}", s.funding_output); - } - } - let mut outputs = outputs_map - .into_iter() - .map(|item| item.1) // a reference to unspent output - .collect::>(); - outputs.sort_unstable_by_key(|out| out.height); - outputs - } - - pub fn hash(&self) -> Option { - let txns = self.history(); - if txns.is_empty() { - None - } else { - let mut hash = FullHash::default(); - let mut sha2 = Sha256::new(); - for item in txns { - let part = format!("{}:{}:", item.tx_hash.to_hex(), item.height); - sha2.input(part.as_bytes()); - } - sha2.result(&mut hash); - Some(hash) - } - } -} - -struct TxnHeight { - txn: Transaction, - height: u32, -} - -fn merklize(left: T, right: T) -> T { - let data = [&left[..], &right[..]].concat(); - ::hash(&data) -} - -fn create_merkle_branch_and_root(mut hashes: Vec, mut index: usize) -> (Vec, T) { - let mut merkle = vec![]; - while hashes.len() > 1 { - if hashes.len() % 2 != 0 { - let last = *hashes.last().unwrap(); - hashes.push(last); - } - index = if index % 2 == 0 { index + 1 } else { index - 1 }; - merkle.push(hashes[index]); - index /= 2; - hashes = hashes - .chunks(2) - .map(|pair| merklize(pair[0], pair[1])) - .collect() - } - (merkle, hashes[0]) -} - -// TODO: the functions below can be part of ReadStore. -fn txrow_by_txid(store: &dyn ReadStore, txid: &Txid) -> Option { - let key = TxRow::filter_full(&txid); - let value = store.get(&key)?; - Some(TxRow::from_row(&Row { key, value })) -} - -fn txrows_by_prefix(store: &dyn ReadStore, txid_prefix: HashPrefix) -> Vec { - store - .scan(&TxRow::filter_prefix(txid_prefix)) - .iter() - .map(|row| TxRow::from_row(row)) - .collect() -} - -fn txids_by_script_hash(store: &dyn ReadStore, script_hash: &[u8]) -> Vec { - store - .scan(&TxOutRow::filter(script_hash)) - .iter() - .map(|row| TxOutRow::from_row(row).txid_prefix) - .collect() -} - -fn txids_by_funding_output( - store: &dyn ReadStore, - txn_id: &Txid, - output_index: usize, -) -> Vec { - store - .scan(&TxInRow::filter(&txn_id, output_index)) - .iter() - .map(|row| TxInRow::from_row(row).txid_prefix) - .collect() -} - -pub struct Query { - app: Arc, - tracker: RwLock, - tx_cache: TransactionCache, - txid_limit: usize, - duration: HistogramVec, -} - -impl Query { - pub fn new( - app: Arc, - metrics: &Metrics, - tx_cache: TransactionCache, - txid_limit: usize, - ) -> Arc { - Arc::new(Query { - app, - tracker: RwLock::new(Tracker::new(metrics)), - tx_cache, - txid_limit, - duration: metrics.histogram_vec( - HistogramOpts::new("electrs_query_duration", "Request duration (in seconds)"), - &["type"], - ), - }) - } - - fn load_txns_by_prefix( - &self, - store: &dyn ReadStore, - prefixes: Vec, - ) -> Result> { - let mut txns = vec![]; - for txid_prefix in prefixes { - for tx_row in txrows_by_prefix(store, txid_prefix) { - let txid: Txid = deserialize(&tx_row.key.txid).unwrap(); - let txn = self.load_txn(&txid, Some(tx_row.height))?; - txns.push(TxnHeight { - txn, - height: tx_row.height, - }) - } - } - Ok(txns) - } - - fn find_spending_input( - &self, - store: &dyn ReadStore, - funding: &FundingOutput, - ) -> Result> { - let spending_txns: Vec = self.load_txns_by_prefix( - store, - txids_by_funding_output(store, &funding.txn_id, funding.output_index), - )?; - let mut spending_inputs = vec![]; - for t in &spending_txns { - for input in t.txn.input.iter() { - if input.previous_output.txid == funding.txn_id - && input.previous_output.vout == funding.output_index as u32 - { - spending_inputs.push(SpendingInput { - txn_id: t.txn.txid(), - height: t.height, - funding_output: (funding.txn_id, funding.output_index), - value: funding.value, - }) - } - } - } - assert!(spending_inputs.len() <= 1); - Ok(if spending_inputs.len() == 1 { - Some(spending_inputs.remove(0)) - } else { - None - }) - } - - fn find_funding_outputs(&self, t: &TxnHeight, script_hash: &[u8]) -> Vec { - let mut result = vec![]; - let txn_id = t.txn.txid(); - for (index, output) in t.txn.output.iter().enumerate() { - if compute_script_hash(&output.script_pubkey[..]) == script_hash { - result.push(FundingOutput { - txn_id, - height: t.height, - output_index: index, - value: output.value, - }) - } - } - result - } - - fn confirmed_status( - &self, - script_hash: &[u8], - ) -> Result<(Vec, Vec)> { - let mut funding = vec![]; - let mut spending = vec![]; - let read_store = self.app.read_store(); - let txid_prefixes = txids_by_script_hash(read_store, script_hash); - // if the limit is enabled - if self.txid_limit > 0 && txid_prefixes.len() > self.txid_limit { - bail!( - "{}+ transactions found, query may take a long time", - txid_prefixes.len() - ); - } - for t in self.load_txns_by_prefix(read_store, txid_prefixes)? { - funding.extend(self.find_funding_outputs(&t, script_hash)); - } - for funding_output in &funding { - if let Some(spent) = self.find_spending_input(read_store, &funding_output)? { - spending.push(spent); - } - } - Ok((funding, spending)) - } - - fn mempool_status( - &self, - script_hash: &[u8], - confirmed_funding: &[FundingOutput], - tracker: &Tracker, - ) -> Result<(Vec, Vec)> { - let mut funding = vec![]; - let mut spending = vec![]; - let txid_prefixes = txids_by_script_hash(tracker.index(), script_hash); - for t in self.load_txns_by_prefix(tracker.index(), txid_prefixes)? { - funding.extend(self.find_funding_outputs(&t, script_hash)); - } - // // TODO: dedup outputs (somehow) both confirmed and in mempool (e.g. reorg?) - for funding_output in funding.iter().chain(confirmed_funding.iter()) { - if let Some(spent) = self.find_spending_input(tracker.index(), &funding_output)? { - spending.push(spent); - } - } - Ok((funding, spending)) - } - - pub fn status(&self, script_hash: &[u8]) -> Result { - let timer = self - .duration - .with_label_values(&["confirmed_status"]) - .start_timer(); - let confirmed = self - .confirmed_status(script_hash) - .chain_err(|| "failed to get confirmed status")?; - timer.observe_duration(); - - let tracker = self.tracker.read().unwrap(); - let timer = self - .duration - .with_label_values(&["mempool_status"]) - .start_timer(); - let mempool = self - .mempool_status(script_hash, &confirmed.0, &tracker) - .chain_err(|| "failed to get mempool status")?; - timer.observe_duration(); - - let mut txn_fees = HashMap::new(); - let funding_txn_ids = mempool.0.iter().map(|funding| funding.txn_id); - let spending_txn_ids = mempool.1.iter().map(|spending| spending.txn_id); - for mempool_txid in funding_txn_ids.chain(spending_txn_ids) { - tracker - .get_fee(&mempool_txid) - .map(|fee| txn_fees.insert(mempool_txid, fee)); - } - - Ok(Status { - confirmed, - mempool, - txn_fees, - }) - } - - fn lookup_confirmed_blockhash( - &self, - tx_hash: &Txid, - block_height: Option, - ) -> Result> { - let blockhash = if self.tracker.read().unwrap().has_txn(&tx_hash) { - None // found in mempool (as unconfirmed transaction) - } else { - // Lookup in confirmed transactions' index - let height = match block_height { - Some(height) => height, - None => { - txrow_by_txid(self.app.read_store(), &tx_hash) - .chain_err(|| format!("not indexed tx {}", tx_hash))? - .height - } - }; - let header = self - .app - .index() - .get_header(height as usize) - .chain_err(|| format!("missing header at height {}", height))?; - Some(*header.hash()) - }; - Ok(blockhash) - } - - // Internal API for transaction retrieval - fn load_txn(&self, txid: &Txid, block_height: Option) -> Result { - let _timer = self.duration.with_label_values(&["load_txn"]).start_timer(); - self.tx_cache.get_or_else(&txid, || { - let blockhash = self.lookup_confirmed_blockhash(txid, block_height)?; - let value: Value = self - .app - .daemon() - .gettransaction_raw(txid, blockhash, /*verbose*/ false)?; - let value_hex: &str = value.as_str().chain_err(|| "non-string tx")?; - hex::decode(&value_hex).chain_err(|| "non-hex tx") - }) - } - - // Public API for transaction retrieval (for Electrum RPC) - pub fn get_transaction(&self, tx_hash: &Txid, verbose: bool) -> Result { - let _timer = self - .duration - .with_label_values(&["get_transaction"]) - .start_timer(); - let blockhash = self.lookup_confirmed_blockhash(tx_hash, /*block_height*/ None)?; - self.app - .daemon() - .gettransaction_raw(tx_hash, blockhash, verbose) - } - - pub fn get_confirmed_blockhash(&self, tx_hash: &Txid) -> Result { - let blockhash = self.lookup_confirmed_blockhash(tx_hash, None)?; - Ok(json!({ "block_hash": blockhash })) - } - - pub fn get_headers(&self, heights: &[usize]) -> Vec { - let _timer = self - .duration - .with_label_values(&["get_headers"]) - .start_timer(); - let index = self.app.index(); - heights - .iter() - .filter_map(|height| index.get_header(*height)) - .collect() - } - - pub fn get_best_header(&self) -> Result { - let last_header = self.app.index().best_header(); - Ok(last_header.chain_err(|| "no headers indexed")?) - } - - pub fn get_merkle_proof( - &self, - tx_hash: &Txid, - height: usize, - ) -> Result<(Vec, usize)> { - let header_entry = self - .app - .index() - .get_header(height) - .chain_err(|| format!("missing block #{}", height))?; - let txids = self.app.daemon().getblocktxids(&header_entry.hash())?; - let pos = txids - .iter() - .position(|txid| txid == tx_hash) - .chain_err(|| format!("missing txid {}", tx_hash))?; - let tx_nodes: Vec = txids - .into_iter() - .map(|txid| TxMerkleNode::from_inner(txid.into_inner())) - .collect(); - let (branch, _root) = create_merkle_branch_and_root(tx_nodes, pos); - Ok((branch, pos)) - } - - pub fn get_header_merkle_proof( - &self, - height: usize, - cp_height: usize, - ) -> Result<(Vec, Sha256dHash)> { - if cp_height < height { - bail!("cp_height #{} < height #{}", cp_height, height); - } - - let best_height = self.get_best_header()?.height(); - if best_height < cp_height { - bail!( - "cp_height #{} above best block height #{}", - cp_height, - best_height - ); - } - - let heights: Vec = (0..=cp_height).collect(); - let header_hashes: Vec = self - .get_headers(&heights) - .into_iter() - .map(|h| *h.hash()) - .collect(); - let merkle_nodes: Vec = header_hashes - .iter() - .map(|block_hash| Sha256dHash::from_inner(block_hash.into_inner())) - .collect(); - assert_eq!(header_hashes.len(), heights.len()); - Ok(create_merkle_branch_and_root(merkle_nodes, height)) - } - - pub fn get_id_from_pos( - &self, - height: usize, - tx_pos: usize, - want_merkle: bool, - ) -> Result<(Txid, Vec)> { - let header_entry = self - .app - .index() - .get_header(height) - .chain_err(|| format!("missing block #{}", height))?; - - let txids = self.app.daemon().getblocktxids(header_entry.hash())?; - let txid = *txids - .get(tx_pos) - .chain_err(|| format!("No tx in position #{} in block #{}", tx_pos, height))?; - - let tx_nodes = txids - .into_iter() - .map(|txid| TxMerkleNode::from_inner(txid.into_inner())) - .collect(); - - let branch = if want_merkle { - create_merkle_branch_and_root(tx_nodes, tx_pos).0 - } else { - vec![] - }; - Ok((txid, branch)) - } - - pub fn broadcast(&self, txn: &Transaction) -> Result { - self.app.daemon().broadcast(txn) - } - - pub fn update_mempool(&self) -> Result<()> { - let _timer = self - .duration - .with_label_values(&["update_mempool"]) - .start_timer(); - self.tracker.write().unwrap().update(self.app.daemon()) - } - - /// Returns [vsize, fee_rate] pairs (measured in vbytes and satoshis). - pub fn get_fee_histogram(&self) -> Vec<(f32, u32)> { - self.tracker.read().unwrap().fee_histogram().clone() - } - - // Fee rate [BTC/kB] to be confirmed in `blocks` from now. - pub fn estimate_fee(&self, blocks: usize) -> f64 { - let mut total_vsize = 0u32; - let mut last_fee_rate = 0.0; - let blocks_in_vbytes = (blocks * 1_000_000) as u32; // assume ~1MB blocks - for (fee_rate, vsize) in self.tracker.read().unwrap().fee_histogram() { - last_fee_rate = *fee_rate; - total_vsize += vsize; - if total_vsize >= blocks_in_vbytes { - break; // under-estimate the fee rate a bit - } - } - (last_fee_rate as f64) * 1e-5 // [BTC/kB] = 10^5 [sat/B] - } - - pub fn get_banner(&self) -> Result { - self.app.get_banner() - } - - pub fn get_relayfee(&self) -> Result { - self.app.daemon().get_relayfee() - } -} diff --git a/src/rpc.rs b/src/rpc.rs deleted file mode 100644 index a69a130..0000000 --- a/src/rpc.rs +++ /dev/null @@ -1,636 +0,0 @@ -use bitcoin::blockdata::transaction::Transaction; -use bitcoin::consensus::encode::{deserialize, serialize}; -use bitcoin::hashes::hex::{FromHex, ToHex}; -use bitcoin::hashes::{sha256d::Hash as Sha256dHash, Hash}; -use error_chain::ChainedError; -use serde_json::{from_str, Value}; -use std::collections::HashMap; -use std::io::{BufRead, BufReader, Write}; -use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream}; -use std::sync::mpsc::{self, Receiver, Sender, SyncSender, TrySendError}; -use std::sync::{Arc, Mutex}; -use std::thread; - -use crate::errors::*; -use crate::metrics::{Gauge, HistogramOpts, HistogramVec, MetricOpts, Metrics}; -use crate::query::{Query, Status}; -use crate::util::{spawn_thread, Channel, HeaderEntry}; - -const ELECTRS_VERSION: &str = env!("CARGO_PKG_VERSION"); -const PROTOCOL_VERSION: &str = "1.4"; - -// TODO: Sha256dHash should be a generic hash-container (since script hash is single SHA256) -fn hash_from_value(val: Option<&Value>) -> Result { - let script_hash = val.chain_err(|| "missing hash")?; - let script_hash = script_hash.as_str().chain_err(|| "non-string hash")?; - let script_hash = T::from_hex(script_hash).chain_err(|| "non-hex hash")?; - Ok(script_hash) -} - -fn usize_from_value(val: Option<&Value>, name: &str) -> Result { - let val = val.chain_err(|| format!("missing {}", name))?; - let val = val.as_u64().chain_err(|| format!("non-integer {}", name))?; - Ok(val as usize) -} - -fn usize_from_value_or(val: Option<&Value>, name: &str, default: usize) -> Result { - if val.is_none() { - return Ok(default); - } - usize_from_value(val, name) -} - -fn bool_from_value(val: Option<&Value>, name: &str) -> Result { - let val = val.chain_err(|| format!("missing {}", name))?; - let val = val.as_bool().chain_err(|| format!("not a bool {}", name))?; - Ok(val) -} - -fn bool_from_value_or(val: Option<&Value>, name: &str, default: bool) -> Result { - if val.is_none() { - return Ok(default); - } - bool_from_value(val, name) -} - -fn unspent_from_status(status: &Status) -> Value { - json!(Value::Array( - status - .unspent() - .into_iter() - .map(|out| json!({ - "height": out.height, - "tx_pos": out.output_index, - "tx_hash": out.txn_id.to_hex(), - "value": out.value, - })) - .collect() - )) -} - -struct Connection { - query: Arc, - last_header_entry: Option, - status_hashes: HashMap, // ScriptHash -> StatusHash - stream: TcpStream, - addr: SocketAddr, - sender: SyncSender, - stats: Arc, - relayfee: f64, -} - -impl Connection { - pub fn new( - query: Arc, - stream: TcpStream, - addr: SocketAddr, - stats: Arc, - relayfee: f64, - sender: SyncSender, - ) -> Connection { - Connection { - query, - last_header_entry: None, // disable header subscription for now - status_hashes: HashMap::new(), - stream, - addr, - sender, - stats, - relayfee, - } - } - - fn blockchain_headers_subscribe(&mut self) -> Result { - let entry = self.query.get_best_header()?; - let hex_header = hex::encode(serialize(entry.header())); - let result = json!({"hex": hex_header, "height": entry.height()}); - self.last_header_entry = Some(entry); - Ok(result) - } - - fn server_version(&self) -> Result { - Ok(json!([ - format!("electrs {}", ELECTRS_VERSION), - PROTOCOL_VERSION - ])) - } - - fn server_banner(&self) -> Result { - Ok(json!(self.query.get_banner()?)) - } - - fn server_donation_address(&self) -> Result { - Ok(Value::Null) - } - - fn server_peers_subscribe(&self) -> Result { - Ok(json!([])) - } - - fn mempool_get_fee_histogram(&self) -> Result { - Ok(json!(self.query.get_fee_histogram())) - } - - fn blockchain_block_header(&self, params: &[Value]) -> Result { - let height = usize_from_value(params.get(0), "height")?; - let cp_height = usize_from_value_or(params.get(1), "cp_height", 0)?; - - let raw_header_hex: String = self - .query - .get_headers(&[height]) - .into_iter() - .map(|entry| hex::encode(&serialize(entry.header()))) - .collect(); - - if cp_height == 0 { - return Ok(json!(raw_header_hex)); - } - let (branch, root) = self.query.get_header_merkle_proof(height, cp_height)?; - - let branch_vec: Vec = branch.into_iter().map(|b| b.to_hex()).collect(); - - Ok(json!({ - "header": raw_header_hex, - "root": root.to_hex(), - "branch": branch_vec - })) - } - - fn blockchain_block_headers(&self, params: &[Value]) -> Result { - let start_height = usize_from_value(params.get(0), "start_height")?; - let count = usize_from_value(params.get(1), "count")?; - let cp_height = usize_from_value_or(params.get(2), "cp_height", 0)?; - let heights: Vec = (start_height..(start_height + count)).collect(); - let headers: Vec = self - .query - .get_headers(&heights) - .into_iter() - .map(|entry| hex::encode(&serialize(entry.header()))) - .collect(); - - if count == 0 || cp_height == 0 { - return Ok(json!({ - "count": headers.len(), - "hex": headers.join(""), - "max": 2016, - })); - } - - let (branch, root) = self - .query - .get_header_merkle_proof(start_height + (count - 1), cp_height)?; - - let branch_vec: Vec = branch.into_iter().map(|b| b.to_hex()).collect(); - - Ok(json!({ - "count": headers.len(), - "hex": headers.join(""), - "max": 2016, - "root": root.to_hex(), - "branch" : branch_vec - })) - } - - fn blockchain_estimatefee(&self, params: &[Value]) -> Result { - let blocks_count = usize_from_value(params.get(0), "blocks_count")?; - let fee_rate = self.query.estimate_fee(blocks_count); // in BTC/kB - Ok(json!(fee_rate.max(self.relayfee))) - } - - fn blockchain_relayfee(&self) -> Result { - Ok(json!(self.relayfee)) // in BTC/kB - } - - fn blockchain_scripthash_subscribe(&mut self, params: &[Value]) -> Result { - let script_hash = - hash_from_value::(params.get(0)).chain_err(|| "bad script_hash")?; - let status = self.query.status(&script_hash[..])?; - let result = status.hash().map_or(Value::Null, |h| json!(hex::encode(h))); - if self - .status_hashes - .insert(script_hash, result.clone()) - .is_none() - { - self.stats.subscriptions.inc(); - } - - Ok(result) - } - - fn blockchain_scripthash_get_balance(&self, params: &[Value]) -> Result { - let script_hash = - hash_from_value::(params.get(0)).chain_err(|| "bad script_hash")?; - let status = self.query.status(&script_hash[..])?; - Ok( - json!({ "confirmed": status.confirmed_balance(), "unconfirmed": status.mempool_balance() }), - ) - } - - fn blockchain_scripthash_get_history(&self, params: &[Value]) -> Result { - let script_hash = - hash_from_value::(params.get(0)).chain_err(|| "bad script_hash")?; - let status = self.query.status(&script_hash[..])?; - Ok(json!(Value::Array( - status - .history() - .into_iter() - .map(|item| item.to_json()) - .collect() - ))) - } - - fn blockchain_scripthash_listunspent(&self, params: &[Value]) -> Result { - let script_hash = - hash_from_value::(params.get(0)).chain_err(|| "bad script_hash")?; - Ok(unspent_from_status(&self.query.status(&script_hash[..])?)) - } - - fn blockchain_transaction_broadcast(&self, params: &[Value]) -> Result { - let tx = params.get(0).chain_err(|| "missing tx")?; - let tx = tx.as_str().chain_err(|| "non-string tx")?; - let tx = hex::decode(&tx).chain_err(|| "non-hex tx")?; - let tx: Transaction = deserialize(&tx).chain_err(|| "failed to parse tx")?; - let txid = self.query.broadcast(&tx)?; - self.query.update_mempool()?; - if let Err(e) = self.sender.try_send(Message::PeriodicUpdate) { - warn!("failed to issue PeriodicUpdate after broadcast: {}", e); - } - Ok(json!(txid.to_hex())) - } - - fn blockchain_transaction_get(&self, params: &[Value]) -> Result { - let tx_hash = hash_from_value(params.get(0)).chain_err(|| "bad tx_hash")?; - let verbose = match params.get(1) { - Some(value) => value.as_bool().chain_err(|| "non-bool verbose value")?, - None => false, - }; - Ok(self.query.get_transaction(&tx_hash, verbose)?) - } - - fn blockchain_transaction_get_confirmed_blockhash(&self, params: &[Value]) -> Result { - let tx_hash = hash_from_value(params.get(0)).chain_err(|| "bad tx_hash")?; - self.query.get_confirmed_blockhash(&tx_hash) - } - - fn blockchain_transaction_get_merkle(&self, params: &[Value]) -> Result { - let tx_hash = hash_from_value(params.get(0)).chain_err(|| "bad tx_hash")?; - let height = usize_from_value(params.get(1), "height")?; - let (merkle, pos) = self - .query - .get_merkle_proof(&tx_hash, height) - .chain_err(|| "cannot create merkle proof")?; - let merkle: Vec = merkle.into_iter().map(|txid| txid.to_hex()).collect(); - Ok(json!({ - "block_height": height, - "merkle": merkle, - "pos": pos})) - } - - fn blockchain_transaction_id_from_pos(&self, params: &[Value]) -> Result { - let height = usize_from_value(params.get(0), "height")?; - let tx_pos = usize_from_value(params.get(1), "tx_pos")?; - let want_merkle = bool_from_value_or(params.get(2), "merkle", false)?; - - let (txid, merkle) = self.query.get_id_from_pos(height, tx_pos, want_merkle)?; - - if !want_merkle { - return Ok(json!(txid.to_hex())); - } - - let merkle_vec: Vec = merkle.into_iter().map(|entry| entry.to_hex()).collect(); - - Ok(json!({ - "tx_hash" : txid.to_hex(), - "merkle" : merkle_vec})) - } - - fn handle_command(&mut self, method: &str, params: &[Value], id: &Value) -> Result { - let timer = self - .stats - .latency - .with_label_values(&[method]) - .start_timer(); - let result = match method { - "blockchain.block.header" => self.blockchain_block_header(¶ms), - "blockchain.block.headers" => self.blockchain_block_headers(¶ms), - "blockchain.estimatefee" => self.blockchain_estimatefee(¶ms), - "blockchain.headers.subscribe" => self.blockchain_headers_subscribe(), - "blockchain.relayfee" => self.blockchain_relayfee(), - "blockchain.scripthash.get_balance" => self.blockchain_scripthash_get_balance(¶ms), - "blockchain.scripthash.get_history" => self.blockchain_scripthash_get_history(¶ms), - "blockchain.scripthash.listunspent" => self.blockchain_scripthash_listunspent(¶ms), - "blockchain.scripthash.subscribe" => self.blockchain_scripthash_subscribe(¶ms), - "blockchain.transaction.broadcast" => self.blockchain_transaction_broadcast(¶ms), - "blockchain.transaction.get" => self.blockchain_transaction_get(¶ms), - "blockchain.transaction.get_merkle" => self.blockchain_transaction_get_merkle(¶ms), - "blockchain.transaction.get_confirmed_blockhash" => self.blockchain_transaction_get_confirmed_blockhash(¶ms), - "blockchain.transaction.id_from_pos" => { - self.blockchain_transaction_id_from_pos(¶ms) - } - "mempool.get_fee_histogram" => self.mempool_get_fee_histogram(), - "server.banner" => self.server_banner(), - "server.donation_address" => self.server_donation_address(), - "server.peers.subscribe" => self.server_peers_subscribe(), - "server.ping" => Ok(Value::Null), - "server.version" => self.server_version(), - &_ => bail!("unknown method {} {:?}", method, params), - }; - timer.observe_duration(); - // TODO: return application errors should be sent to the client - Ok(match result { - Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}), - Err(e) => { - warn!( - "rpc #{} {} {:?} failed: {}", - id, - method, - params, - e.display_chain() - ); - json!({"jsonrpc": "2.0", "id": id, "error": format!("{}", e)}) - } - }) - } - - fn update_subscriptions(&mut self) -> Result> { - let timer = self - .stats - .latency - .with_label_values(&["periodic_update"]) - .start_timer(); - let mut result = vec![]; - if let Some(ref mut last_entry) = self.last_header_entry { - let entry = self.query.get_best_header()?; - if *last_entry != entry { - *last_entry = entry; - let hex_header = hex::encode(serialize(last_entry.header())); - let header = json!({"hex": hex_header, "height": last_entry.height()}); - result.push(json!({ - "jsonrpc": "2.0", - "method": "blockchain.headers.subscribe", - "params": [header]})); - } - } - for (script_hash, status_hash) in self.status_hashes.iter_mut() { - let status = self.query.status(&script_hash[..])?; - let new_status_hash = status.hash().map_or(Value::Null, |h| json!(hex::encode(h))); - if new_status_hash == *status_hash { - continue; - } - result.push(json!({ - "jsonrpc": "2.0", - "method": "blockchain.scripthash.subscribe", - "params": [script_hash.to_hex(), new_status_hash]})); - *status_hash = new_status_hash; - } - timer.observe_duration(); - Ok(result) - } - - fn send_values(&mut self, values: &[Value]) -> Result<()> { - for value in values { - let line = value.to_string() + "\n"; - self.stream - .write_all(line.as_bytes()) - .chain_err(|| format!("failed to send {}", value))?; - } - Ok(()) - } - - fn handle_replies(&mut self, receiver: Receiver) -> Result<()> { - let empty_params = json!([]); - loop { - let msg = receiver.recv().chain_err(|| "channel closed")?; - trace!("RPC {:?}", msg); - match msg { - Message::Request(line) => { - let cmd: Value = from_str(&line).chain_err(|| "invalid JSON format")?; - let reply = match ( - cmd.get("method"), - cmd.get("params").unwrap_or_else(|| &empty_params), - cmd.get("id"), - ) { - ( - Some(&Value::String(ref method)), - &Value::Array(ref params), - Some(ref id), - ) => self.handle_command(method, params, id)?, - _ => bail!("invalid command: {}", cmd), - }; - self.send_values(&[reply])? - } - Message::PeriodicUpdate => { - let values = self - .update_subscriptions() - .chain_err(|| "failed to update subscriptions")?; - self.send_values(&values)? - } - Message::Done => return Ok(()), - } - } - } - - fn parse_requests(mut reader: BufReader, tx: SyncSender) -> Result<()> { - loop { - let mut line = Vec::::new(); - reader - .read_until(b'\n', &mut line) - .chain_err(|| "failed to read a request")?; - if line.is_empty() { - tx.send(Message::Done).chain_err(|| "channel closed")?; - return Ok(()); - } else { - if line.starts_with(&[22, 3, 1]) { - // (very) naive SSL handshake detection - let _ = tx.send(Message::Done); - bail!("invalid request - maybe SSL-encrypted data?: {:?}", line) - } - match String::from_utf8(line) { - Ok(req) => tx - .send(Message::Request(req)) - .chain_err(|| "channel closed")?, - Err(err) => { - let _ = tx.send(Message::Done); - bail!("invalid UTF8: {}", err) - } - } - } - } - } - - pub fn run(mut self, receiver: Receiver) { - let reader = BufReader::new(self.stream.try_clone().expect("failed to clone TcpStream")); - let sender = self.sender.clone(); - let child = spawn_thread("reader", || Connection::parse_requests(reader, sender)); - if let Err(e) = self.handle_replies(receiver) { - error!( - "[{}] connection handling failed: {}", - self.addr, - e.display_chain().to_string() - ); - } - self.stats - .subscriptions - .sub(self.status_hashes.len() as i64); - debug!("[{}] shutting down connection", self.addr); - let _ = self.stream.shutdown(Shutdown::Both); - if let Err(err) = child.join().expect("receiver panicked") { - error!("[{}] receiver failed: {}", self.addr, err); - } - } -} - -#[derive(Debug)] -pub enum Message { - Request(String), - PeriodicUpdate, - Done, -} - -pub enum Notification { - Periodic, - Exit, -} - -pub struct RPC { - notification: Sender, - server: Option>, // so we can join the server while dropping this ojbect -} - -struct Stats { - latency: HistogramVec, - subscriptions: Gauge, -} - -impl RPC { - fn start_notifier( - notification: Channel, - senders: Arc>>>, - acceptor: Sender>, - ) { - spawn_thread("notification", move || { - for msg in notification.receiver().iter() { - let mut senders = senders.lock().unwrap(); - match msg { - Notification::Periodic => { - senders.retain(|sender| { - if let Err(TrySendError::Disconnected(_)) = - sender.try_send(Message::PeriodicUpdate) - { - false // drop disconnected clients - } else { - true - } - }) - } - Notification::Exit => acceptor.send(None).unwrap(), // mark acceptor as done - } - } - }); - } - - fn start_acceptor(addr: SocketAddr) -> Channel> { - let chan = Channel::unbounded(); - let acceptor = chan.sender(); - spawn_thread("acceptor", move || { - let listener = - TcpListener::bind(addr).unwrap_or_else(|e| panic!("bind({}) failed: {}", addr, e)); - info!( - "Electrum RPC server running on {} (protocol {})", - addr, PROTOCOL_VERSION - ); - loop { - let (stream, addr) = listener.accept().expect("accept failed"); - stream - .set_nonblocking(false) - .expect("failed to set connection as blocking"); - acceptor.send(Some((stream, addr))).expect("send failed"); - } - }); - chan - } - - pub fn start(addr: SocketAddr, query: Arc, metrics: &Metrics, relayfee: f64) -> RPC { - let stats = Arc::new(Stats { - latency: metrics.histogram_vec( - HistogramOpts::new("electrs_electrum_rpc", "Electrum RPC latency (seconds)"), - &["method"], - ), - subscriptions: metrics.gauge(MetricOpts::new( - "electrs_electrum_subscriptions", - "# of Electrum subscriptions", - )), - }); - stats.subscriptions.set(0); - let notification = Channel::unbounded(); - - RPC { - notification: notification.sender(), - server: Some(spawn_thread("rpc", move || { - let senders = Arc::new(Mutex::new(Vec::>::new())); - - let acceptor = RPC::start_acceptor(addr); - RPC::start_notifier(notification, senders.clone(), acceptor.sender()); - - let mut threads = HashMap::new(); - let (garbage_sender, garbage_receiver) = crossbeam_channel::unbounded(); - - while let Some((stream, addr)) = acceptor.receiver().recv().unwrap() { - // explicitely scope the shadowed variables for the new thread - let query = Arc::clone(&query); - let stats = Arc::clone(&stats); - let garbage_sender = garbage_sender.clone(); - let (sender, receiver) = mpsc::sync_channel(10); - - senders.lock().unwrap().push(sender.clone()); - - let spawned = spawn_thread("peer", move || { - info!("[{}] connected peer", addr); - let conn = Connection::new(query, stream, addr, stats, relayfee, sender); - conn.run(receiver); - info!("[{}] disconnected peer", addr); - let _ = garbage_sender.send(std::thread::current().id()); - }); - - trace!("[{}] spawned {:?}", addr, spawned.thread().id()); - threads.insert(spawned.thread().id(), spawned); - while let Ok(id) = garbage_receiver.try_recv() { - if let Some(thread) = threads.remove(&id) { - trace!("[{}] joining {:?}", addr, id); - if let Err(error) = thread.join() { - error!("failed to join {:?}: {:?}", id, error); - } - } - } - } - trace!("closing {} RPC connections", senders.lock().unwrap().len()); - for sender in senders.lock().unwrap().iter() { - let _ = sender.send(Message::Done); - } - for (id, thread) in threads { - trace!("joining {:?}", id); - if let Err(error) = thread.join() { - error!("failed to join {:?}: {:?}", id, error); - } - } - - trace!("RPC connections are closed"); - })), - } - } - - pub fn notify(&self) { - self.notification.send(Notification::Periodic).unwrap(); - } -} - -impl Drop for RPC { - fn drop(&mut self) { - trace!("stop accepting new RPCs"); - self.notification.send(Notification::Exit).unwrap(); - if let Some(handle) = self.server.take() { - handle.join().unwrap(); - } - trace!("RPC server is stopped"); - } -} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..9cb5b3b --- /dev/null +++ b/src/server.rs @@ -0,0 +1,201 @@ +use anyhow::{Context, Result}; +use bitcoin::BlockHash; +use bitcoincore_rpc::RpcApi; +use crossbeam_channel::{bounded, select, unbounded, Receiver, Sender}; +use rayon::prelude::*; +use serde_json::{de::from_str, Value}; + +use std::{ + collections::hash_map::HashMap, + convert::TryFrom, + io::{BufRead, BufReader, Write}, + net::{Shutdown, TcpListener, TcpStream}, + thread, +}; + +use crate::{ + config::Config, + daemon::rpc_connect, + electrum::{Client, Rpc}, + signals, +}; + +fn spawn(name: &'static str, f: F) -> thread::JoinHandle<()> +where + F: 'static + Send + FnOnce() -> Result<()>, +{ + thread::Builder::new() + .name(name.to_owned()) + .spawn(move || { + if let Err(e) = f() { + warn!("{} thread failed: {}", name, e); + } + }) + .expect("failed to spawn a thread") +} + +struct Peer { + client: Client, + stream: TcpStream, +} + +impl Peer { + fn new(stream: TcpStream) -> Self { + Self { + client: Client::default(), + stream, + } + } +} + +fn tip_receiver(config: &Config) -> Result> { + let (tip_tx, tip_rx) = bounded(0); + let rpc = rpc_connect(&config)?; + + let duration = u64::try_from(config.wait_duration.as_millis()).unwrap(); + + use crossbeam_channel::TrySendError; + spawn("tip_loop", move || loop { + let tip = rpc.get_best_block_hash()?; + match tip_tx.try_send(tip) { + Ok(_) | Err(TrySendError::Full(_)) => (), + Err(TrySendError::Disconnected(_)) => bail!("tip receiver disconnected"), + } + rpc.wait_for_new_block(duration)?; + }); + Ok(tip_rx) +} + +pub fn run(config: &Config, mut rpc: Rpc) -> Result<()> { + let listener = TcpListener::bind(config.electrum_rpc_addr)?; + let tip_rx = tip_receiver(&config)?; + info!("serving Electrum RPC on {}", listener.local_addr()?); + + let (server_tx, server_rx) = unbounded(); + spawn("accept_loop", || accept_loop(listener, server_tx)); // detach accepting thread + let signal_rx = signals::register(); + + let mut peers = HashMap::::new(); + loop { + select! { + recv(signal_rx) -> sig => { + match sig.context("signal channel disconnected")? { + signals::Signal::Exit => break, + signals::Signal::Trigger => (), + } + }, + recv(tip_rx) -> tip => match tip { + Ok(_) => (), // sync and update + Err(_) => break, // daemon is shutting down + }, + recv(server_rx) -> event => { + let event = event.context("server disconnected")?; + let buffered_events = server_rx.iter().take(server_rx.len()); + for event in std::iter::once(event).chain(buffered_events) { + handle(&rpc, &mut peers, event); + } + }, + }; + rpc.sync().context("rpc sync failed")?; + peers + .par_iter_mut() + .map(|(peer_id, peer)| { + let notifications = rpc.update_client(&mut peer.client)?; + send(*peer_id, peer, ¬ifications) + }) + .collect::>()?; + } + info!("stopping Electrum RPC server"); + Ok(()) +} + +struct Event { + peer_id: usize, + msg: Message, +} + +enum Message { + New(TcpStream), + Request(String), + Done, +} + +fn handle(rpc: &Rpc, peers: &mut HashMap, event: Event) { + match event.msg { + Message::New(stream) => { + debug!("{}: connected", event.peer_id); + peers.insert(event.peer_id, Peer::new(stream)); + } + Message::Request(line) => { + let result = match peers.get_mut(&event.peer_id) { + Some(peer) => handle_request(rpc, event.peer_id, peer, line), + None => { + warn!("{}: unknown peer for {}", event.peer_id, line); + Ok(()) + } + }; + if let Err(e) = result { + error!("{}: {}", event.peer_id, e); + let _ = peers + .remove(&event.peer_id) + .map(|peer| peer.stream.shutdown(Shutdown::Both)); + } + } + Message::Done => { + debug!("{}: disconnected", event.peer_id); + peers.remove(&event.peer_id); + } + } +} + +fn handle_request(rpc: &Rpc, peer_id: usize, peer: &mut Peer, line: String) -> Result<()> { + let request: Value = from_str(&line).with_context(|| format!("invalid request: {}", line))?; + let response: Value = rpc + .handle_request(&mut peer.client, request) + .with_context(|| format!("failed to handle request: {}", line))?; + send(peer_id, peer, &[response]) +} + +fn send(peer_id: usize, peer: &mut Peer, values: &[Value]) -> Result<()> { + for value in values { + let mut response = value.to_string(); + debug!("{}: send {}", peer_id, response); + response += "\n"; + peer.stream + .write_all(response.as_bytes()) + .with_context(|| format!("failed to send response: {}", response))?; + } + Ok(()) +} + +fn accept_loop(listener: TcpListener, server_tx: Sender) -> Result<()> { + for (peer_id, conn) in listener.incoming().enumerate() { + let stream = conn.context("failed to accept")?; + let tx = server_tx.clone(); + spawn("recv_loop", move || { + let result = recv_loop(peer_id, &stream, tx); + let _ = stream.shutdown(Shutdown::Both); + result + }); + } + Ok(()) +} + +fn recv_loop(peer_id: usize, stream: &TcpStream, server_tx: Sender) -> Result<()> { + server_tx.send(Event { + peer_id, + msg: Message::New(stream.try_clone()?), + })?; + let reader = BufReader::new(stream); + for line in reader.lines() { + let line = line.with_context(|| format!("{}: recv failed", peer_id))?; + debug!("{}: recv {}", peer_id, line); + let msg = Message::Request(line); + server_tx.send(Event { peer_id, msg })?; + } + server_tx.send(Event { + peer_id, + msg: Message::Done, + })?; + Ok(()) +} diff --git a/src/signal.rs b/src/signal.rs deleted file mode 100644 index 269e4e1..0000000 --- a/src/signal.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crossbeam_channel as channel; -use crossbeam_channel::RecvTimeoutError; -use std::thread; -use std::time::Duration; - -use crate::errors::*; - -#[derive(Clone)] // so multiple threads could wait on signals -pub struct Waiter { - receiver: channel::Receiver, -} - -fn notify(signals: &[i32]) -> channel::Receiver { - let (s, r) = channel::bounded(1); - let signals = - signal_hook::iterator::Signals::new(signals).expect("failed to register signal hook"); - thread::spawn(move || { - for signal in signals.forever() { - s.send(signal) - .unwrap_or_else(|_| panic!("failed to send signal {}", signal)); - } - }); - r -} - -impl Waiter { - pub fn start() -> Waiter { - Waiter { - receiver: notify(&[ - signal_hook::SIGINT, - signal_hook::SIGTERM, - signal_hook::SIGUSR1, // allow external triggering (e.g. via bitcoind `blocknotify`) - ]), - } - } - pub fn wait(&self, duration: Duration) -> Result<()> { - match self.receiver.recv_timeout(duration) { - Ok(sig) => { - trace!("notified via SIG{}", sig); - if sig != signal_hook::SIGUSR1 { - bail!(ErrorKind::Interrupt(sig)) - }; - Ok(()) - } - Err(RecvTimeoutError::Timeout) => Ok(()), - Err(RecvTimeoutError::Disconnected) => bail!("signal hook channel disconnected"), - } - } - pub fn poll(&self) -> Result<()> { - self.wait(Duration::from_secs(0)) - } -} diff --git a/src/signals.rs b/src/signals.rs new file mode 100644 index 0000000..4bcc43c --- /dev/null +++ b/src/signals.rs @@ -0,0 +1,30 @@ +use crossbeam_channel::{unbounded, Receiver}; +use signal_hook::consts::signal::*; +use signal_hook::iterator::Signals; + +use std::thread; + +pub(crate) enum Signal { + Exit, + Trigger, +} + +pub(crate) fn register() -> Receiver { + let ids = [ + SIGINT, SIGTERM, + SIGUSR1, // allow external triggering (e.g. via bitcoind `blocknotify`) + ]; + let (tx, rx) = unbounded(); + let mut signals = Signals::new(&ids).expect("failed to register signal hook"); + thread::spawn(move || { + for id in &mut signals { + info!("notified via SIG{}", id); + let signal = match id { + SIGUSR1 => Signal::Trigger, + _ => Signal::Exit, + }; + tx.send(signal).expect("failed to send signal"); + } + }); + rx +} diff --git a/src/status.rs b/src/status.rs new file mode 100644 index 0000000..df7e226 --- /dev/null +++ b/src/status.rs @@ -0,0 +1,365 @@ +use anyhow::Result; +use bitcoin::{ + hashes::{sha256, Hash, HashEngine}, + Amount, Block, BlockHash, OutPoint, Transaction, Txid, +}; +use rayon::prelude::*; +use serde_json::{json, Value}; + +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::convert::TryFrom; + +use crate::{ + cache::Cache, + chain::Chain, + daemon::Daemon, + index::Index, + mempool::Mempool, + merkle::Proof, + types::{ScriptHash, StatusHash}, +}; + +#[derive(Default)] +struct Entry { + outputs: Vec, + spent: Vec, +} + +struct TxEntry { + txid: Txid, + outputs: Vec, + spent: Vec, +} + +impl TxEntry { + fn new(txid: Txid, entry: Entry) -> Self { + Self { + txid, + outputs: entry.outputs, + spent: entry.spent, + } + } +} + +pub(crate) struct ConfirmedEntry { + txid: Txid, + height: usize, +} + +impl ConfirmedEntry { + pub fn hash(&self, engine: &mut sha256::HashEngine) { + let s = format!("{}:{}:", self.txid, self.height); + engine.input(s.as_bytes()); + } + + pub fn value(&self) -> Value { + json!({"tx_hash": self.txid, "height": self.height}) + } +} + +pub(crate) struct MempoolEntry { + txid: Txid, + has_unconfirmed_inputs: bool, + fee: Amount, +} + +impl MempoolEntry { + fn height(&self) -> isize { + match self.has_unconfirmed_inputs { + true => -1, + false => 0, + } + } + + pub fn hash(&self, engine: &mut sha256::HashEngine) { + let s = format!("{}:{}:", self.txid, self.height()); + engine.input(s.as_bytes()); + } + + pub fn value(&self) -> Value { + json!({"tx_hash": self.txid, "height": self.height(), "fee": self.fee.as_sat()}) + } +} + +/// ScriptHash subscription status +pub struct Status { + scripthash: ScriptHash, + tip: BlockHash, + statushash: Option, + confirmed: HashMap>, + mempool: Vec, +} + +fn make_outpoints<'a>(txid: &'a Txid, outputs: &'a [u32]) -> impl Iterator + 'a { + outputs.iter().map(move |vout| OutPoint::new(*txid, *vout)) +} + +impl Status { + pub fn new(scripthash: ScriptHash) -> Self { + Self { + scripthash, + tip: BlockHash::default(), + statushash: None, + confirmed: HashMap::new(), + mempool: Vec::new(), + } + } + + fn filter_outputs(&self, tx: &Transaction) -> Vec { + let outputs = tx.output.iter().zip(0u32..); + outputs + .filter_map(move |(txo, vout)| { + if ScriptHash::new(&txo.script_pubkey) == self.scripthash { + Some(vout) + } else { + None + } + }) + .collect() + } + + fn filter_inputs(&self, tx: &Transaction, outpoints: &HashSet) -> Vec { + tx.input + .iter() + .filter_map(|txi| { + if outpoints.contains(&txi.previous_output) { + Some(txi.previous_output) + } else { + None + } + }) + .collect() + } + + fn funding_confirmed(&self, chain: &Chain) -> HashSet { + self.confirmed + .iter() + .filter_map(|(blockhash, entries)| chain.get_block_height(blockhash).map(|_| entries)) + .flat_map(|entries| { + entries + .iter() + .flat_map(|entry| make_outpoints(&entry.txid, &entry.outputs)) + }) + .collect() + } + + pub(crate) fn get_unspent(&self, chain: &Chain) -> HashSet { + let mut unspent: HashSet = self.funding_confirmed(chain); + unspent.extend( + self.mempool + .iter() + .flat_map(|entry| make_outpoints(&entry.txid, &entry.outputs)), + ); + + let spent_outpoints = self + .confirmed + .iter() + .filter_map(|(blockhash, entries)| { + chain.get_block_height(blockhash).map(|_height| entries) + }) + .flatten() + .chain(self.mempool.iter()) + .flat_map(|entry| entry.spent.iter()); + for outpoint in spent_outpoints { + assert!(unspent.remove(outpoint), "missing outpoint {}", outpoint); + } + unspent + } + + pub(crate) fn get_confirmed(&self, chain: &Chain) -> Vec { + self.confirmed + .iter() + .filter_map(move |(blockhash, entries)| { + chain + .get_block_height(blockhash) + .map(|height| (height, &entries[..])) + }) + .collect::>() + .into_iter() + .flat_map(|(height, entries)| { + entries.iter().map(move |e| ConfirmedEntry { + txid: e.txid, + height, + }) + }) + .collect() + } + + pub(crate) fn get_mempool(&self, mempool: &Mempool) -> Vec { + let mut entries = self + .mempool + .iter() + .filter_map(|e| mempool.get(&e.txid)) + .collect::>(); + entries.sort_by_key(|e| (e.has_unconfirmed_inputs, e.txid)); + entries + .into_iter() + .map(|e| MempoolEntry { + txid: e.txid, + has_unconfirmed_inputs: e.has_unconfirmed_inputs, + fee: e.fee, + }) + .collect() + } + + fn for_new_blocks(&self, blockhashes: B, daemon: &Daemon, func: F) -> Result<()> + where + B: IntoIterator, + F: FnMut(BlockHash, Block), + { + daemon.for_blocks( + blockhashes + .into_iter() + .filter(|blockhash| !self.confirmed.contains_key(blockhash)), + func, + ) + } + + fn sync_confirmed( + &self, + index: &Index, + daemon: &Daemon, + cache: &Cache, + outpoints: &mut HashSet, + ) -> Result>> { + type PosTxid = (u32, Txid); + let mut result = HashMap::>::new(); + + let funding_blockhashes = index.filter_by_funding(self.scripthash); + self.for_new_blocks(funding_blockhashes, daemon, |blockhash, block| { + let txids: Vec = block.txdata.iter().map(|tx| tx.txid()).collect(); + for (pos, (tx, txid)) in block.txdata.into_iter().zip(txids.iter()).enumerate() { + let funding_outputs = self.filter_outputs(&tx); + if funding_outputs.is_empty() { + continue; + } + cache.add_tx(*txid, move || tx); + cache.add_proof(blockhash, *txid, || Proof::create(&txids, pos)); + outpoints.extend(make_outpoints(&txid, &funding_outputs)); + result + .entry(blockhash) + .or_default() + .entry((u32::try_from(pos).unwrap(), *txid)) + .or_default() + .outputs = funding_outputs; + } + })?; + let spending_blockhashes: HashSet = outpoints + .par_iter() + .flat_map_iter(|outpoint| index.filter_by_spending(*outpoint)) + .collect(); + self.for_new_blocks( + spending_blockhashes.into_iter(), + daemon, + |blockhash, block| { + let txids: Vec = block.txdata.iter().map(|tx| tx.txid()).collect(); + for (pos, (tx, txid)) in block.txdata.into_iter().zip(txids.iter()).enumerate() { + let spent_outpoints = self.filter_inputs(&tx, &outpoints); + if spent_outpoints.is_empty() { + continue; + } + cache.add_tx(*txid, move || tx); + cache.add_proof(blockhash, *txid, || Proof::create(&txids, pos)); + result + .entry(blockhash) + .or_default() + .entry((u32::try_from(pos).unwrap(), *txid)) + .or_default() + .spent = spent_outpoints; + } + }, + )?; + + Ok(result + .into_iter() + .map(|(blockhash, entries_map)| { + let sorted_entries = entries_map + .into_iter() + .collect::>() + .into_iter() + .map(|((_pos, txid), entry)| TxEntry::new(txid, entry)) + .collect::>(); + (blockhash, sorted_entries) + }) + .collect()) + } + + fn sync_mempool( + &self, + mempool: &Mempool, + cache: &Cache, + outpoints: &mut HashSet, + ) -> Vec { + let mut result = HashMap::::new(); + for entry in mempool.filter_by_funding(&self.scripthash) { + let funding_outputs = self.filter_outputs(&entry.tx); + assert!(!funding_outputs.is_empty()); + outpoints.extend(make_outpoints(&entry.txid, &funding_outputs)); + result.entry(entry.txid).or_default().outputs = funding_outputs; + cache.add_tx(entry.txid, || entry.tx.clone()); + } + for entry in outpoints + .iter() + .flat_map(|outpoint| mempool.filter_by_spending(outpoint)) + { + let spent_outpoints = self.filter_inputs(&entry.tx, &outpoints); + assert!(!spent_outpoints.is_empty()); + result.entry(entry.txid).or_default().spent = spent_outpoints; + cache.add_tx(entry.txid, || entry.tx.clone()); + } + result + .into_iter() + .map(|(txid, entry)| TxEntry::new(txid, entry)) + .collect() + } + + fn compute_status_hash(&self, chain: &Chain, mempool: &Mempool) -> Option { + let confirmed = self.get_confirmed(chain); + let mempool = self.get_mempool(mempool); + if confirmed.is_empty() && mempool.is_empty() { + return None; + } + let mut engine = StatusHash::engine(); + for entry in confirmed { + entry.hash(&mut engine); + } + for entry in mempool { + entry.hash(&mut engine); + } + Some(StatusHash::from_engine(engine)) + } + + pub(crate) fn sync( + &mut self, + index: &Index, + mempool: &Mempool, + daemon: &Daemon, + cache: &Cache, + ) -> Result<()> { + let mut outpoints: HashSet = self.funding_confirmed(index.chain()); + + let new_tip = index.chain().tip(); + if self.tip != new_tip { + let update = self.sync_confirmed(index, daemon, cache, &mut outpoints)?; + self.confirmed.extend(update); + self.tip = new_tip; + } + if !self.confirmed.is_empty() { + debug!( + "{} transactions from {} blocks", + self.confirmed.values().map(Vec::len).sum::(), + self.confirmed.len() + ); + } + self.mempool = self.sync_mempool(mempool, cache, &mut outpoints); + if !self.mempool.is_empty() { + debug!("{} mempool transactions", self.mempool.len()); + } + self.statushash = self.compute_status_hash(index.chain(), mempool); + Ok(()) + } + + pub fn statushash(&self) -> Option { + self.statushash + } +} diff --git a/src/store.rs b/src/store.rs deleted file mode 100644 index 912133c..0000000 --- a/src/store.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::path::{Path, PathBuf}; - -use crate::util::Bytes; - -#[derive(Clone)] -pub struct Row { - pub key: Bytes, - pub value: Bytes, -} - -impl Row { - pub fn into_pair(self) -> (Bytes, Bytes) { - (self.key, self.value) - } -} - -pub trait ReadStore: Sync { - fn get(&self, key: &[u8]) -> Option; - fn scan(&self, prefix: &[u8]) -> Vec; -} - -pub trait WriteStore: Sync { - fn write>(&self, rows: I); - fn flush(&self); -} - -#[derive(Clone)] -struct Options { - path: PathBuf, - bulk_import: bool, - low_memory: bool, -} - -pub struct DBStore { - db: rocksdb::DB, - opts: Options, -} - -impl DBStore { - fn open_opts(opts: Options) -> Self { - debug!("opening DB at {:?}", opts.path); - let mut db_opts = rocksdb::Options::default(); - db_opts.create_if_missing(true); - // db_opts.set_keep_log_file_num(10); - db_opts.set_max_open_files(if opts.bulk_import { 16 } else { 256 }); - db_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level); - db_opts.set_compression_type(rocksdb::DBCompressionType::Snappy); - db_opts.set_target_file_size_base(256 << 20); - db_opts.set_write_buffer_size(256 << 20); - db_opts.set_disable_auto_compactions(opts.bulk_import); // for initial bulk load - db_opts.set_advise_random_on_open(!opts.bulk_import); // bulk load uses sequential I/O - if !opts.low_memory { - db_opts.set_compaction_readahead_size(1 << 20); - } - - let mut block_opts = rocksdb::BlockBasedOptions::default(); - block_opts.set_block_size(if opts.low_memory { 256 << 10 } else { 1 << 20 }); - DBStore { - db: rocksdb::DB::open(&db_opts, &opts.path).unwrap(), - opts, - } - } - - /// Opens a new RocksDB at the specified location. - pub fn open(path: &Path, low_memory: bool) -> Self { - DBStore::open_opts(Options { - path: path.to_path_buf(), - bulk_import: true, - low_memory, - }) - } - - pub fn enable_compaction(self) -> Self { - let mut opts = self.opts.clone(); - if opts.bulk_import { - opts.bulk_import = false; - info!("enabling auto-compactions"); - let opts = [("disable_auto_compactions", "false")]; - self.db.set_options(&opts).unwrap(); - } - self - } - - pub fn compact(self) -> Self { - info!("starting full compaction"); - self.db.compact_range(None::<&[u8]>, None::<&[u8]>); // would take a while - info!("finished full compaction"); - self - } - - pub fn iter_scan(&self, prefix: &[u8]) -> ScanIterator { - ScanIterator { - prefix: prefix.to_vec(), - iter: self.db.prefix_iterator(prefix), - done: false, - } - } -} - -pub struct ScanIterator<'a> { - prefix: Vec, - iter: rocksdb::DBIterator<'a>, - done: bool, -} - -impl<'a> Iterator for ScanIterator<'a> { - type Item = Row; - - fn next(&mut self) -> Option { - if self.done { - return None; - } - let (key, value) = self.iter.next()?; - if !key.starts_with(&self.prefix) { - self.done = true; - return None; - } - Some(Row { - key: key.to_vec(), - value: value.to_vec(), - }) - } -} - -impl ReadStore for DBStore { - fn get(&self, key: &[u8]) -> Option { - self.db.get(key).unwrap().map(|v| v.to_vec()) - } - - // TODO: use generators - fn scan(&self, prefix: &[u8]) -> Vec { - let mut rows = vec![]; - for (key, value) in self.db.iterator(rocksdb::IteratorMode::From( - prefix, - rocksdb::Direction::Forward, - )) { - if !key.starts_with(prefix) { - break; - } - rows.push(Row { - key: key.to_vec(), - value: value.to_vec(), - }); - } - rows - } -} - -impl WriteStore for DBStore { - fn write>(&self, rows: I) { - let mut batch = rocksdb::WriteBatch::default(); - for row in rows { - batch.put(row.key.as_slice(), row.value.as_slice()).unwrap(); - } - let mut opts = rocksdb::WriteOptions::new(); - opts.set_sync(!self.opts.bulk_import); - opts.disable_wal(self.opts.bulk_import); - self.db.write_opt(batch, &opts).unwrap(); - } - - fn flush(&self) { - let mut opts = rocksdb::WriteOptions::new(); - opts.set_sync(true); - opts.disable_wal(false); - let empty = rocksdb::WriteBatch::default(); - self.db.write_opt(empty, &opts).unwrap(); - } -} - -impl Drop for DBStore { - fn drop(&mut self) { - trace!("closing DB at {:?}", self.opts.path); - } -} - -fn full_compaction_marker() -> Row { - Row { - key: b"F".to_vec(), - value: b"".to_vec(), - } -} - -pub fn full_compaction(store: DBStore) -> DBStore { - store.flush(); - let store = store.compact().enable_compaction(); - store.write(vec![full_compaction_marker()]); - store -} - -pub fn is_fully_compacted(store: &dyn ReadStore) -> bool { - let marker = store.get(&full_compaction_marker().key); - marker.is_some() -} diff --git a/src/tests/fixtures/incomplete_block.hex b/src/tests/fixtures/incomplete_block.hex deleted file mode 100644 index 6ad1acc..0000000 --- a/src/tests/fixtures/incomplete_block.hex +++ /dev/null @@ -1 +0,0 @@ -0b110907594b000000000020641d419ee2a526faf346ba2102318e60dfef7c4bec34c54860440000000000002a164f117ae7d7fc02cfbe3783edd8ec2827996748d3d1f91d9fcd7716e64a1135eeab5bffff001d99fe35204701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff23030a951500b20000006376652d323031382d31373134342d6b6873396e650001000000ffffffff01e0d5b704000000001976a9142b2399d24a9e9e35487d2f76b57d6e33e72d522f88ac000000000100000001681bf79e10b3063c3e38df2a69af48bc239543ca665cb53b0a6b56fa0a703478000000006a47304402201bd46ddbc6cc4dffd97bfac4ebde57048f16e8e283aca22b6c5c1247f01e66cb02207ff18b456dd3154798940f8b355a9b5ca42ef567dad0e10f303324abacc7daf00121026f2372d18aec8af7625901b86dea891e47c091c291385fbbe2009f7f62ec8ccbffffffff0280778e06000000001976a91454fd8b0df3f5608c30f63c92737b4d94dce1089288acc8d35905000000001976a9145ded65ce1d4f12ac1b61a7cf9316f0986d7a727388ac0000000001000000011d89de90af96327b7116caa40c042201d8eb7a1ca94c0d73f466b5b4248d4acd00000000fdf20100473044022032eca96a589243751cf1777393f164f72a7ccb935acede87f9b5e665b5d4e486022065bbb60662050f1c640444a766fdfb51a565bbdb1b93bbbff38048f7dc500f6e0147304402201d06b9b2528ee40cdc6fd278655171b7c4adb142e0d5fb16eaebedf3378e5a7a02200789d214c3af7e349caf82410e18439e5f4adeded9080e072f4ce70727d931eb0147304402202b6486184b76892a999370a1d3d146fa18e0906c080548fc28ccaa7bbf349a19022011684de70092c69a8be759cb7220d70e174982b1a3a410df30332dedb493f21c0147304402205a627351dc5d5db863ebd72e13121ec49eef0102706fe3397d428dd5c6044c7c022051c45ccb2bffa2f4103ab53b7f85f03096323b765398e1ea7433a5d3774130a7014ccf5421025a2b368ff7d6241dcd5eea41263ff5ca2c3e800c7d8b64a8c64aebb35fe594142102890330f2926bc246f7dc0d0caaff8020208c1b03a80fbc3fb7bea306662dcc3f2102a66109d1f299a31795dd383ecad5296b10e5490f4ac95e9d9e69faafcff62c422102aa50692021cfc39e8c790e3d0639fd5a9a0be510121eae691a0c82a7f8ad0b082103083e4f5024b6abde562ffb809f08846b05af55f4753903b78881c11d623f56e021030e9449ac79868f961dd4d1f831c34eab7a86f5e63c0cffdd8a055ebcb8e95c1956aeffffffff0140420f000000000017a9148bd65a11eeba4d17174be0b6e2646c9cd136bcd687000000000100000001149dd9d9d9a9948f3c838afdcd06d1bab7bc97a94bd024237a0d48fb00aa75e300000000fdfd0000483045022100ce83b44e54db4b93e6f604d7a3c16fb84fd09fce5c0f66b0fac5d46784361ddf02205866cf3d8c71bc6b44b71c771df9526ec2f3c81743a3efa3f9aa7856703e718f01473044022062dbeb24587f38a0d21e808782b901ff71e84b57cfa04230233913c76dfb899e0220367f260e221b937ff56efc1183bb1b98a1737a2fe0fd4772224632193f3cf0ec014c6952210223c7e10e8484fdb1173d26073a6f2ef97ba7f7c5e69b8d518cec2ce0eb3f5ae72102e9607ca33fe444a5ba63c23134d7a4230b9a25aa22d8ea1cca2a64bb147c4cf921033fe8e6b1dfc74afb19593292435ff58a43e93ff6d041bcdf7f3633c34301d32e53aeffffffff01a0bb0d000000000017a914f9546de8cec4722a05475188505f36f36a3a82ff87000000000100000002751b3e7fdc9e93c8cd160ac34d921272c319d09615f9a92300d40b3311b6cf39020000006a4730440220141e6870dfb8f7caba0c1f6154956407f22d9fe910416f1e09e928279ea25ecd022015fe40b6bfc763f12403280a6f770e58d6d5eb90bd0d68e7f74cce11a34e830601210206bc89bb04c2507e868b3adfa231614a10e09ee2ba9f59f225e406e981316749ffffffffdf4b109a06ca44ac1fcba171d0f685c138be4eaac7c875ba05d9c596884ae368000000006a473044022022c81864a7d7a57a88a4e4ee9b2b27e28832f801c57d50c8faf425fc37f4fc860220528fbda19ca795136d1273c3027a5fe1ed398214d955ca2075b5f439f3c630b90121034dd87503de3cd7c7b11605598f6d2f2625fb18b0ea8689e5faed71b289a8890effffffff03f4e20000000000001976a91451690a1f7809b457dc3704d56675161e2aeb015d88ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363a0860100000000001976a91451690a1f7809b457dc3704d56675161e2aeb015d88ac000000000100000002cee99c974ff3f20db97b06326171a77712570c08b385c8c2f9c4104a4bbdc3c0020000006a47304402204e86f204a173bcf30fddd7d837955ca8da5cf05cff87ce68820626143430b5ec02203f0a4575b11b4444fd2b9e48e18cc7843ed24fccd7d0b5152a871e56c1d816ad01210369f3e1fb89eb8c7d3bfe252c1475d3904b80d8fdc486a046b11eeeb75ea76fd5ffffffffdd23131cf7048d42737d20c4137575e1c360d811b8161842b9fb57a8576585ce000000006a473044022062b25120d0d51f7137bf3c1584d4a3b381e3d98d4034eb342d5cd2c2e7f6c71702206e72c9f078c42ec463fc48ef3a486da08835e11ce1a1d3ff9ae36d4a7b8b4df701210374ced7eeb305adf2f84ba1131a96149390b5b0c5f15fc20d888c871830e2d50dffffffff03a0860100000000001976a914cdf4956c5a87b0209b2109b4f131f4375d6f01a488ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363f4e20000000000001976a914cdf4956c5a87b0209b2109b4f131f4375d6f01a488ac0000000001000000016e0abe3d22c36255a28ceca641fe2da5177d6be5bcb3cbbd3921279eeacd9376010000006a4730440220775bd16035fa6712ad82aa0c6d70e4e3a0fc11511e315de82d813f859b63e53a02203f1e1a8994ea8de2062cf3f17f50c1946f6031defe0b6db521e1c166a0257b68012102000c67dcab04b843c8883d9f4199db0faa5a4a538193d77c35117e5e74f3b1fcffffffff029409e606000000001976a91402957c157f7d49c910dc8474d2a3def5dbf9ad3688acf3dd0000000000001976a914e324cb583b2e779c0b6e5d34478315d38243351b88ac000000000100000003c2a5f58cba71f51a88b2b0e3a5410051256dacbb3456e1ae58a2326634b50bc9020000006a4730440220544299560b5c8813c98ff0d4abe7c3aa3f6137c63fba2ce602d264687c361ff9022056872d4afc83299067cdf510c0a5ddf035109e16885f352bc5ef4f9a4512f180012103430da78b979aa181a6d95b716671a2d732dc95eedc8cf3d0c55e8cf7fc4d8c2affffffff34b6499766aceff41dc3654afec3f343ea37b2a0864a88e201422e545e2369d4020000006a4730440220361da04dbda41dd629289a4f09d7546fcfce35df275042b3ee6d87f4deebbc61022034d5dfe9ecf8f94b89e8085d867637da1a88ed4fdd65f01fc2a3c4b21985c00e012103b8815f5e2785ad92378ac5d77ca935e088a1932194cd0f4974ac83ebcf980122ffffffffbaee9874f9e1acf24c300ef7c9ae83f92dd9d53adefa7303dd5f75095fc2bd0c020000006b48304502210084055251fef4039acb225c2a5710608e725f9a27cae06b2cdf87fb61559f84f902206c5db9fcdc14716ae238aed89705a0d7c32f368c260fe616a175cdbc9fdb11280121022c38ebf67e79ff6ff917ca8576dd09969dfe90dc67b3ed27a3abb42557f9d3f7ffffffff030000000000000000226a20396663633637363165343863343661336230356630346337326263643830636308e80000000000001976a9149447d8c8c0f785368b78bfeffe449079023d895f88aca0860100000000001976a9149447d8c8c0f785368b78bfeffe449079023d895f88ac0000000001000000012a4e7eeb59824683dc6c6b3972bb8656b834abe014da094087b98a4e138bbe01000000006a473044022006f22e4eb582a829e771ae173dcefd8d11bd723a803bc59d50706351cd7324e402203303a63b155e54c556337122cb43abe58b201cb2cbe9b929c17108456d8e9198012103b8560979e215dda98409a81602b8c910b4d05b3ccf16ff5d18ebd83fe183c168ffffffff034366e303000000001976a9143c929cbc61ab77d5a92592952f962b4a9876569688aca0860100000000001976a9143c929cbc61ab77d5a92592952f962b4a9876569688ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363000000000100000002a26b5d257c5cb7786ca31bde0d225cb340fad977a21db3bb0f2c2472533ac9ba000000006b483045022100c68f9ebf90e0bf7b4c995f1f72af7c802c864fb7ee56951d90929859b506d80002204dfefb0d03a5a48d2ee5b6b747666e255a1051c3f1548f71ee100c55066e4a3e012103b4102e6a2e4900e21d6fcae84589c1247469eea1d2196ff97629975f3ddd1781ffffffff883003bb4637417791eddf6ed266f7f83716f4465fd812982dc6e52387cae7c3010000006a4730440220257c30fd86374cfeb14ae994cc3b982f4183b1c944d6796b59c618a48c9386fb022063ec9c31301f340b4acd6069ec7d0ae5c5d2bc28aec44629ea61c7dc2ff1cd020121030aaebb62ad2c05aab09b06e09d0efa0fd96405df412f9986d63f73d9e5ebe0faffffffff03483f0000000000001976a91422ec05316457ef93e72b61f5620dde70f7f6eda288ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363a0860100000000001976a91422ec05316457ef93e72b61f5620dde70f7f6eda288ac000000000100000002b2255e4e31698c69f28b58168bf95f4c73b6211ded364d87305214f4f8c35b6f020000006a47304402205e419ad055a868dbf4bb684d9cc3a9c3a734038279b6634d1cf65a2d28a7f763022030f2d6ef098ba3c80ea2ddbbf76e7ea7d46cab4cb700a31ea7d8bf5ca49943b50121021f5530263acf6811b87d29b0c00810c8cc3937b0883ba38019a1de505e8b252dffffffff4e1ee6cc05cc6f73d7868fd3153105701f505957f356a25092293a6d20d57a76020000006b483045022100e9ce0d418b97aeb25252da166fddd04e1f7eaf540d0cb9c8a2e60e14ea1c059302203539ce4f2203b4001656ae9cc9dfed645ae03e3fe44b2b4ba65af4a6fd4808550121036d753e70f5a2a09374dc000db39182b24dd464c6cfd045ee926f914843e1c05dffffffff03f4e20000000000001976a914fd245130cf010237d41c586de0177fd4163257aa88aca0860100000000001976a914fd245130cf010237d41c586de0177fd4163257aa88ac0000000000000000226a2039666363363736316534386334366133623035663034633732626364383063630000000001000000047f47b7033c16c675b86e72d54b4cec78f343df12219ae591ec30c034888f233b010000006a47304402202816f7b343ff968c4d9a2b49a43e13adf4728beb9c0af582c6615177e78a579f0220150eb74379fd94fa7b13be6b1aaf9ff9d54e2045a40b0180dd152596b811aa6e012102ee7e97bf363818a62371b501d167ca7db4f9854bcbd4c2dd7df34bb61e556f3affffffffe477844e31fb572cd56aab8a3993f5e8eb597838489736fa1517a73fb5f568bd000000006b483045022100e2275a2c96b64533f6c4ca93d5a0b1f6318c3af137d7945a2a34e046453e04c402203db28930ea68f3586dfaf7a2b4ef942f0997e83a4cfcd7416fd501e013a8573a01210367513ed7edd3ff97fd5f2757d18cc1b03ec0d5ccb4249b40d576aeb11b0e7e1bffffffff96c435a4783d562159ee4280944d7dc5fba1dfd34cde8bf088eccb44ac350604020000006b483045022100db16447476595476b256e34ad84f1dbe28df2e3fdfd490a643d393252a0a3e430220577cda42b47c852baba76c5a27bb409807fd72ec1fa3720d91d17d9fe15f66f501210240986b842aebb629ca75e9802fb7d10ae01e2d0b3423422e203780eeef13e4d1ffffffff9c9f126eae43fa7153ebb37ab5522ee9fdf96fca7e96fbd622366fe6eee32a1a000000006b483045022100a3799eaa57a2fa658a58c17e648f790b733035276cade3006fd288934c5971760220533d249dfff615b9d1b3cc6efaf04a939f3460a9f610a9cdeef8b2330335ef6e0121025e33b298b7bf7839b6aee984b96a474ddb2b1db07be7b35e311d12287aded95affffffff030000000000000000226a203966636336373631653438633436613362303566303463373262636438306363a0860100000000001976a914d6a3796e626ec3d74772e6ef5b312b2e90727dd288ac58fc0000000000001976a914d6a3796e626ec3d74772e6ef5b312b2e90727dd288ac000000000100000002090f8cde924c83d60368058a01312c43c2247efcb93e21d431d9742d2cce7662000000006b483045022100ccfd64b9f88df815ca6e9ea91bf64b45901b44d52bba38e83a96c93630c8f0ba022070cbda19f592178616e32c3e60ed9fdafea8c0b2eda0f3ec297bf16db277b2be012103ec954d736bfaa868f7c179d49de24fb2e56204d998a128cd421c51759f1f93cdffffffff50160324ed1a1d47d08f81429f4b7ac4bdd3bfc377e74c1caa34b370dfe541f5020000006b483045022100a0012ca53b8b4f47b068afea33919873c2069ee0f8bab5983441a310214338320220156799b36162084b7734581634ffcbc2ebc13ecfabaa7f4eab5c9ca7e562dc2a01210310fb33c60e74576656edf26a38ed621a10b66fbaaab216a2a990a8e7696233a5ffffffff030000000000000000226a203966636336373631653438633436613362303566303463373262636438306363a0860100000000001976a91475e0ee0d9f069f9ce230a9abc4a0ec0a2e7a3ac688acf4e20000000000001976a91475e0ee0d9f069f9ce230a9abc4a0ec0a2e7a3ac688ac000000000100000002fa88c55bd7ba348c27d735e4c453c917cd5b46e57c40e027622dc0931e449c10010000006b483045022100a85bcf2c62d086ec2e12b5a5991930989b8e07d453e1918cf1deb0844a4216b20220591cc977baf0fd74244851eae8f461339f66df5d36f9674252f25161d9bef22401210220281cce072917c62ec2f0ff6ce67969063fd5c80e8e224d5d17d59000f94d92ffffffff406a0fa9576f939a9c4cabda4a208e843b35ef2c60d5059b2c11248a7aba3338020000006b4830450221009969d453e091e856a49f6a562129f76c54f9c15e3cd95873a61e86f99b35ec7c02206cceac0f72322a0bd438775b0c0d63e6f61b741ea2e9eda2f0f74704589d0c81012102b2dcc31a809cc5e498bccfef2bfcad29f6a2d47464cd972e5940bfb0e9b51e63ffffffff03f4e20000000000001976a914570eb26943bfadf565720aa0cef04788bbf0730f88ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363a0860100000000001976a914570eb26943bfadf565720aa0cef04788bbf0730f88ac000000000100000002f87586ac35db267516e3baf367a85d947aa4ecd7ee36baa68c5a9ee78b34c946000000006b48304502210086212f0051b07a144e74154b31fa8af086c5d8b173f2d5843681c105f58dcbf7022070e017393154da5e2a30a826acdd0d305e28108490b21e7ce5ec3a2717e2dfab0121032e3baee29a5d82ea7bfdace8cd663684012467f5ba70bf931343383fb720592dffffffff4911fdb95720ed8ca78b240e4aa4631fd51c62ccd5c1b49cd1b1d06dd047824e020000006b483045022100923432d7ba233f1ef2b7b06cbc40b0f81afee026793632abe30a1320c53174ff02200d0a1b42152daf7f5650614c5e9d52ccf4178a80c3950a79212cccd67e07d0e4012102404cb71762d3fb8c679d01186e7919ec2018bfe536888d8d4d0a49f77c076294ffffffff030000000000000000226a203966636336373631653438633436613362303566303463373262636438306363a0860100000000001976a91446e7b52d2b5d93fd5c1984d851008226e6ca304988acf4e20000000000001976a91446e7b52d2b5d93fd5c1984d851008226e6ca304988ac00000000010000000160cd70bf548305ffdf29f2dd9418dcff8d9ec38af429fd7bb79c8a2e2ab08eba000000006b4830450221008074a75e4cad91e908d0f7927e5a5fed1c5593003af8008d716bddd4b8bd0bd6022044d452feb7f59324963a894bbcb2678d7d150998b38cf7e9effd3b83279ad4780121039e9b34b43c4cc20e081963a512955dd81e3e66a12fa30a65ccc6d1fefe77aa4affffffff0219120602000000001976a914e974df25d5057c86c1b19dbb7f9bb854aacbf0ec88ac80969800000000001976a914e532632693f447d0ecff642bb92fef36c9b71d1188ac0000000001000000014cb55d45c6f9ad6a22c3a48973ce869d0727c6e38934bcb3a6856f1d7ac9d07e000000006b483045022100f98650d457ebeb13831d9638a3332201b4e22dfc9bf2ed9c38da71387c2b24e802202806ef967387936b9b5fae5f766e9d6114a8f58cbdec6bf07c15d33514a9a66f01210287c58fa83b712e801ce67b536546b0bc7f69dc39519958e317335e55d1827db8ffffffff0280969800000000001976a914e532632693f447d0ecff642bb92fef36c9b71d1188aced226d01000000001976a91408414122e0df74552182a17206ba69482aad141288ac000000000100000001e038d0d8bab20b1c63698952cce4b1b27fdc16dd465ed272d81feae44e3fe84e010000006b483045022100c85bcad3ccdf6b9edadd589fd8e35850a76b9bd55d4a380f6f3481886034dc1a0220146de6bdbca23877647edb4f55b841398f7c30a2d16fdcf10f4ddc6f98e0a9d6012103a0778bde75d67426ca521c4305f6dc9340ac0b723e3d6377b0a37fec6bccd01dffffffff02c133d400000000001976a91436d37e7f08ffeb1905a18e565b75a17bf3df4f2588ac80969800000000001976a914e532632693f447d0ecff642bb92fef36c9b71d1188ac0000000001000000012cccd5b3817460f76363647090dfcdc18721c3fbe39630cbc02f33de2d5d12cc010000006b483045022100d9bc3a17f3d57d9b37d4162e6309864be1b48cc813f2e60a00061e6068ca6b490220412a80086b45ee9393b82c26b0df54e6231468d56a8cab7a663d139bf3eb2002012102c2a93a9fba8af1f603564e66731e31c1b2fc178c4980d563ac15b131dfad0889ffffffff0210270000000000001976a9148cbb5e1800cffa93701d747c0d6fe927ec8adfc088ac539bce03000000001976a9148ea97e533889be4932ad306ae15002348e83351f88ac0000000001000000012161596486f6aac9b4abb58ddecd5e00b02d68cbc9eb4f51dce7a487a36d329101000000fc00473044022025f8623f54dc215bd6fa4434a47c8c216ec9d525b9a521e060f206879f9bbc2b02203bfb296723b7a974370e99ae33a246641631d1029e2baf2874f7111161011a4c01473044022033b95e79ead9dc1ff63f8b9d25f16d0f737df85611a8b5543e5c79da70bdca2202206c9ccd5505d0682975f7f8b3ad984ee1a025418e554ed16d9775d15f1cdd0fe2014c695221024b32734b14ae63998cf42857b3a5b46308203893a34062310702b80da70da75c210281221d073c7ca7093356db9b54d0345c9e124f3936dea34399fa4fb6b7fba056210335ed765b07c61bae70bc539913b2355aecfdfacff14c5338e998b3372f1d022653aeffffffff01273f55000000000017a914d60c1cfed051c6c62a749115cd6a14a385d6c38b870a9515000100000001027668dd2cea72366fff4d29740ad4902224b7d1a2da33ea9eea9c9c6158c067000000006a473044022032e9f10d6d1c354b359f38c993986c60f18ffe51231f29872c640f7387b4462a022060062040aa519ec7a06d7a2ae756183813c009a188e72fc5a00d2591ca7c11870121039c1c48e1069421e9ae2d2df297acb89100a785e5891ef3b7d82758339e1dfe17ffffffff01c6b25900000000001976a914ee0af6b6473ad097d34ab14603c7880e6323d4a788ac000000000200000001050583a999ef13aa72205248b7aafc0ba27b8c3e89fa4434a7255d0a3f3fb7b3000000006b483045022100d5a0811ce9e30a1a576e975971b269d9078f14a3a367c22c981c6cf2d86b3c830220751ee97d4fbc708f1f7eaf9df6affe48eb77458943a8167ef1a5e69815a4c454012103bb375f34fdcb85bc7a498f0549a422e2349f167565dfe5c1750ef92bb0ed8d22ffffffff01f452ba0f000000001976a914bce45708265a68e8260da5f2a176584562d7d39288ac0000000001000000015f2f5b078082007127afd7339a8ed5cfc2830e18cf8444f75dfbc26bac083d90010000006a473044022060ab89bbf2ee50f4c15ea00aabbbb115b3dd6b6f7527568103c105df71b59f280220343442e7138b6a76d653e7e77ed8d0681cf79256b6f26b6aa624dc184a50dfd2012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acc4de412f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000015f881d19fed77fda0cdbc44972d32c6e165f714d7ce8fede298bddb7ee2f32be010000006a47304402206be965558059cd197d83319d8711e434a6cc43d27cbce0537e184df576d77b5e02203bb0a355bc6c5e3285b5fb1c5c3edda7cc10eda00075cd184629e03f00ca79d1012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac3c54402f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001ab184c7a673e50f5159362c67102f8ccca59a3e0201592ba682d8bc72245ac06010000006b4830450221008e8937cef937446b8fc13b640360ac87835311708365f12e0e5a745cb40293650220309ad4796bb031a3e46d50a974e3e0fabaa58bfe78e2b15e9c188350c04181df012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acb4c93e2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000114af248dd8e41f0e66c0c9276056168587d7dd269e3ac7abd57381a6755a84d2010000006a47304402203fd6e752e9fa438a231d972f301bd801dae37182939d8ca68a6d27987274d04002205c23f01210b2626c7530f448c861624b756de0cc1f2e8780997e2f2693ced1b7012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac2c3f3d2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000111caa902697623faa6b9c4b2262c332941693c574a05eea1e683f7f8bd9bde68010000006a47304402203dbd700e7d66791ccc2bf8f8185883b307cb6dca1615ecf8b5315751565d561d02200ff16bd420ecc58e993c0973b580d9a452eae961e668cba6f66a06a985c847df012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88aca4b43b2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000011dfd149f7917fadb2b0bae681a137e9d44e677972d4ba2ec8007b5eb5d1b3fe9010000006a473044022062bf0f6d6e6b1c8077b36a2172b6e6cdcd128859ee9798c2f9f31ac7f3e7ab5a02204d9fcc4a2fb4685017ddda477c7b20df4738fdbc59c83f61f50d5d81050924c5012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac1c2a3a2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000019bae06e8931f0b2126312252223f3f168c678b87536bb38a6664b10d0754b330010000006a47304402202f2913c20b0e0a2c985a563d004e54f06787b65e5282bb1a1b6a4661866e51db022060b01274c198500fd9057f8f617f8a91fb79bb7e94b4ce0757c21ded5c2d1dac012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac949f382f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000163b229df7b9abd5b085155dff7bdc63412205c535e50aa18ea583293484206d9010000006a47304402203ef95dd4279b94273f583a30fd54effeb29db05a774d37d942083c59e522aecc0220760a23b811fa52d1e6d6303992fb8c96fb278129f5bfea2992c5825a0e7568f1012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac0c15372f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001e40d4d5b9826df8f4472b71ad93585ff1f08121265315396532719f634c38f31010000006a4730440220155d481aa766990c9e93691c2269e73a2d899e4f7d3fd50f631277077282176f02206537a1fff3c8af101137e0ab348485b419ff6eaed0e289d6573d1adf4b8b0121012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac848a352f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000104fba622222f65d6c80cc70246ea3642ffe8cd1e33c863f5bf8e08dda23cea04010000006a47304402207812e9d1d5175be3f869f07b93365b74b3467bd9eda5b6e28e9727b3d26a986a02207d0828063eb93b010e4fd8caabc39956ba2670f88d70df713ad9ddabb90e2a41012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acfcff332f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001405d036e940e741ac571e9cc246ba0150b14bf511de3ced167692b269753ca72010000006a47304402206c75eac612abecc29f82d815a5b4fdebf92f33601e1b9f3143ba42dfccdd22ab022016c313803835e06e9a0c3bc1369799730833676ffe98fe52963e5d47e3ab0d4c012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac7475322f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000010132b695690171a29dd38814c7b2157b0354d9faca89607430e378ed0c2be534010000006b483045022100e56bee5399bc847c0614fcd949c244ff4e87d59dd0b04e51d3dc70439516e1690220217772297a7af5095a609fb4192c23f1416fdf3a8cc89d1d54ffc66b229495ea012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acecea302f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000101b2730fcff87422848ed2a23e4586b0f3339d7b2edf7a4e612a5057eeccc65b010000006a473044022040298ab6802072bdd2b8cd866037a499e4afca2dd5cd875be637f775ade5df1a02206bd8f0e07108d19be37c3cffcf33bf5aaa6554b1e25e314dfac0fd61c108e562012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac64602f2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001d4b3f63a466df84891e870482829dd1f56d8a0cf082c978479b1cabfd00f5d29010000006b483045022100eadb2ca0162db834a6272a50a5fa390f7ce8250d2d9734a160106a8ea9b1853602204b3d864a06762d75c40b51b073ac032ee252988dfc4cbd214f0b2586d21fa3c3012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acdcd52d2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001acca0ebabe111e81e5a6dbe21b1daa68a245304ee06788c4aa589758e95f17e2010000006a47304402201b61e073897dd9c260d5760f210cca0878781a705090c9f3feeda3f5da32a040022074a3340efe0cf975e5c36cadeb464c695be6a4057a05a29dedec0b9c17fd5328012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac544b2c2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000170976943a33ef3270062c40c58d92a2deba129385218052cf7ecf0cc4b22d46a010000006a473044022072b02e14a1f8a4376d4a4152831cfa708588cf83a8bfa589a3e070a02c54b9d202203e0ea5514c190a99052dbd889ffb87b6ec6f8fa4048c99d57cb635a465ed3916012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acccc02a2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001bd791aa5c5ceae9e2ff6c7630a0d9e40ad640f44fd346d8e4176b5e2faf26c7e000000006b483045022100bdd0c9f12213e47fd1618d88dde7198833b379c0b4e810f104a2b393e1346b3c022025577353e26ba9d838907da7043c305140b82d9bfca815edadd38194e76760b1012103e5885b6d071114a19698194505176570a9202a5aa447d85e771f0390e9b167f3ffffffff027c8d1801000000001976a914364ba78ded72c30fe40c371d01a08559430cdc5588ace8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac000000000100000001b92291a908d52b05e0fbc761954362694e79f48a507c069936c3cc435c957125000000006b483045022100f971f62580aaa9ffff7cefabe4389a11610bad73ce41223fe53fb9d32b9809ce022039da71e82bf68f4a6ed5f86d77020dcef7f4b6f87c2b5db844c8b7ab49a455ce012103449f6e9a4be4dacc6c5299714745279e79c2096599cfce614a8e26acc42c1d13ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488acac851801000000001976a914e6d1ba95fd3b68a0804fec39bcf47a26bfbe3fbc88ac000000000100000001e8e4ac82d830e73a8884fcfa2792bab704c30939aad1ca78d45e9e3675f8e65c010000006a473044022078313d2922fc0beaf1118a3c343ad9c52dcd0c310ee579dc3d45f4a7cab24a6f0220395640883dc2a6b50dcd4a42c09fd5e3b49e84820923e3d3006ef5c30a140d03012102c09fce8583e9023069aee1a309c5084312b44c27aaa93a23807a6777ce388700ffffffff02dc7d1801000000001976a914bdd475bae576a1e98e3f60f6ff0386d009217dbc88ace8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac000000000100000001739c71dce44b225a983223020d3981a536e1d584bd8e775e27bf469f28b82ef1000000006a4730440220580b55574abd0465fb8318a17d471309321677131a5d891b77cb50331f706231022070ffdee7813936993e9684e532d95600a798842e0e978cc3db8435938573e1d50121025a50ea5d095fd9efbb3d6fdd968e3f33eb25545dbf71e3324d8d25ce79702e79ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac0c761801000000001976a91406dcf9cfc46792800f97b700d179b5663d65dbf488ac000000000100000001ee9e9aba1703ad72040b50fbf6903703c9e6ae9c48db70cc34677eeb7e9c62a5010000006b483045022100ee0f12e48e3e0fb8fcefa8c00504652d151824b5e7cfaa0dc1b302aaf05ad70b02201271bf84df770fd55261eaadea8ade4f4d332b6a66106769bc5ef081b4dca0fb012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac4436292f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000010ac75c5e47b01c05deaaa6aec17a13e796c52088f02a9251cbc2a50f057a4da8010000006b483045022100c002a11c00f1915e067a29dfe64587eee2c9b18c8271b51157eff46dc924c78202203190ad31326d5fb8ab5db25c17f62091c9e0326eb434c99b75cdd3b3aebe5fc9012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acbcab272f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001fb952bbb53c3f23f7575c7dbb73c4eb74b63cd80642dbc39aa185ae40775fa03010000006a47304402200f5b2e4993cf995f6d3128baed19499baa68cbf60697f90ebe4fdb85b767cf5a022018d406993d6ff280afb4b4a48743a229beb84cdaa63a0e665e4ddc188ab0d500012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac3421262f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001a770429ad8d6b4b156add8b76b80d2339fd1747798514455925498b9099d291a010000006a473044022060934e29efdff34da5846cbc2a6ebcf34cbf898ba56786292ec25a17ae625559022066f74d6e1b4195435a90a743c33447b067ee448f76107013e09a923c3aeb0203012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acac96242f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000012d4a4ee1ac91cc30ce1e12b729e6e0241a371e791ba7aa62b9dbfea3696cde1f010000006b483045022100ed55a47b3147083d907f8d621599ba1e21c5cdd9727aa6ecf8fa01cfc41af19902204bae02ac080a0d0246403c5deafb5124011179443e383e9a1ce41731c350e90f012102b8fe2708c547bc5f25c8f50e8993853d58f2ec3b28f09e1586d2473a4365ad89ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac3c6e1801000000001976a91409793d6a686985cca8b36482425a9f5954271ffa88ac00000000010000000174d05dfd37bd5b18f2554338477a073920f42e596211ab7f4370b9d2b3b6565e010000006b4830450221008d8c265ef477d181cffdb119131bab6de7233f0854f2293a236cc2d669bba6e002201e6ff66bc910803289714f9d186112a648600517201949ba2fb8ad375a7ae0d801210278f0627a137db9849074bc0149a8ebc7f90901ccee0858c684f894a142fd813effffffff026c661801000000001976a914a60799e1c61ee3eab94c81533aeca01e355c08a288ace8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac000000000100000001a6689d3dcd2c0b5fa6dcde35d61285542f87de19782d2c3b6977061d88297156000000006b483045022100fb54b3c69b79b8c5b513aafd58eba5f97357ca09929245bc15f3af915fa43d24022070b2323152576389efbc0adae4e4f975285061f747adc7d1d856200d9a8dd065012102191fd9583c909d40094c9804eb6967e067af3f96167186ac1407598992d88704ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac9c5e1801000000001976a9149c143f77effa9507846db3d3c543eeaca79fbc5b88ac00000000010000000107eb6b7f8553419cb0dfbfd222eb19f1f360ec546da0ff9283882ea1bba40a9f010000006b483045022100fbb6b86e9d209f7e26aeb8a549f8652da76a0dd16c6b3535d31cb6b32ffd12c502203ed1194083fb5c7cc0ff268bb9bfbef298ab5bf657c225d6ba1f6e9f5daf4ee00121024b3190982fe56d805e51c2d4db1dac00e0eaf12181b7450fa9976da553434093ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488accc561801000000001976a9140c9135ba474f6800419e38bb7a38d10108b4846688ac0000000001000000015153bc1d6fc6482d908fccf08ee660c4bd8b282f82044432401aa0f6ed37a91b010000006b483045022100dfd04c01b719775143e2b79f1c7032156ef246ddf7ddb428f214cc39bc335c28022034b881c14981a38b39c08e3af01ab9c7882c4bcfa9a18ddec5e5f1a61287eebe01210313be3112da036cd7bda9fa6ea48738daee7bf03b634a9d549ef783c33fea6803ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488acfc4e1801000000001976a91428e80ccda48eca649f8245859838c8f2eef8c11e88ac00000000010000000162ddf366c547e194d371f0e706a1febae1c2732b3e79f664712adfc62cde4456010000006a473044022045817da5817a360bdaf70aeb536a2c6f4eb015ab7e98576d69a3c1b445445fd402202eea3c1bcaae57bf6133d64e63072e74bf5b0e12edfca2e471d94ff5925abf0b01210226b856556f0c3805eeedb89f85ad0d1e9c702aea3e87e8910e5e812b217ae4ffffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac2c471801000000001976a914108b0dae808ab114026f90bd063bde84cb1da1fe88ac0000000001000000010ee6f542ab9f34e23570f21d8d9683766fb08ee5608a6ed9ea842195180c8fa0010000006b48304502210086aa89a0f992b6801d36adc8d20fac0e991d57646e39471bbe2997559b131baa0220019d44a9c035e5c9ea0da3f3c941f937b1b43c406b4c0aa67ac51578931b7bab012103efbeaead9ea8a68866055d0c57aec643fa402269a68cc85a2a8ea6121f791047ffffffff025c3f1801000000001976a914f71090c16bdbc6dbe9759655a9693b841760c30288ace8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac0000000001000000017c6f209a6680b74320aae3c8225284bf8a512d3408d6348735b3a7a51dee6bcc000000006b483045022100ecd3d4771edbf2e8f3a008abc4a2fa8402c1791ba35b41b6728938da74d885c302205b9a568b82e665f42f1ed0532fb456ab0379a3a3d8b98f0a125c1b367357ce26012103a2312e8f6eff2f7545478675ab6fbaec8d9009254a8c3a61d32345508a3cc726ffffffff028c371801000000001976a9141c466747588b93a4589acea0d0a08f7f5906ed3d88ace8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac0000000001000000015ff68b3193502bdda4eb102e81603b52a557436611ae12b2645407615e77e8ae000000006a47304402202c17dc24452543a64c77a46ed5929614f77c46b653aa909d1ff970ffb719f96b02203bf0e8bb48fc4202cde3fff7790d51e5d4d5eca4077effc35ace76500d86209f01210263d4da919f9e3947f522d06a0fbae0ce4479d9793f5e755895eed59bddd3147fffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488acbc2f1801000000001976a914c6d03ca3fb3119feb9e63659de13893ff76ddf7388ac000000000100000001de2b671543dffdc126452400a26084c06f59afdd11f51a8fcf0ea3a762563539010000006b483045022100dc3d37bb29bcdb9915d5f6d8dcb610ae425e75960658defd9375f10a13c6fb6902201fcc1c8dc7c08264ae27cc6cb6c2b0710690d17a38ac1eb9bbeb7409e26b85670121028af97c0e56c2b23512a48451c0efd52d09cea8cd744aaa8cb42ce5ca78869a78ffffffff02ec271801000000001976a91439fe94936daf12aa51294253a82e841989e6bc3888ace8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac0000000001000000017aec4434ab1d55afb0cb929d4ba4829b4c222127fdbf066b6510976567f9475c000000006a47304402207f0fdec40b8bbc27d6022a40a7230e800c3f9aa80e144fd63eeabff8404d364402205d3dd9b93471d11fdce779240c0da6b4e2046765b6dce9fa5b69aaeee7a94f580121029388055b690ea332e96ce7153bc59f0197de347c544ef2f99099776883230ac8ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac1c201801000000001976a91496ca47c026546285a50c4a24aca6b654a494414c88ac000000000100000001c082f493b83c5044693064347a2f5aea49a95a2c571356ef4d51cfff87dbd99f010000006a473044022075eb5fd0e23101cde6b074c0f66b7831be4dbc869b5c4fe194898d8b0a330b3502203682096ae860af9dca7af3918b93ee89b8aae3c19665f5ec0fd7ac0f5b0e2bfe012102e0c08d7db315613eb27f8e4b1982423f2e4d872f31930b307bd215d776a8b973ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac4c181801000000001976a9141a22627a1951e428dbdbe45ae96393f5a96478d888ac000000000100000001bebc869ea08fc5fda6d8ba071710b62a874d7ed6f7df032e6059c39627ef3af0010000006a4730440220447c2878df7576d4a6711b3b107fad0ef129892b086828bb8b144b14791a7bc002202406a6e4584b080cf691d3cab3530d024a823d91a25304daf8c0684d7348dad80121037d3c4d0a0d60ec564def68678c74aea7bf8055bd5a546c1e332706cc14a39ab9ffffffff027c101801000000001976a914733c9f242747c33089ec9db78479f1870544b58e88ace8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac0000000001000000015763ca2f5fbc1adbd12264eddb4ea0a9deefc43a70fa0ed56fa37b2ffd1c0c64000000006a4730440220041fba52c38c9d4f37237a1d979f3b8085ba4aa9d9fb96d6b3614e6c21dbc71702206fb98e9f2a20cd0461676a4e04b99dbbd1fc05c6c048ef1ff6279692ebd3d729012103047d01e43ec1ffdab175c5103dda944526fd3b30e9b5546cf6c47b6b0be73b71ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488acac081801000000001976a914a102c762f4f408f74efe8b0c1568119100ed937288ac000000000100000001f78a62ff183f8f66cd71b0d35ad39d43afc3fb6a0c79f2120f89777eeff71599010000006b483045022100987a9941fa16fe9f88510b7934223d5ac4e63ead293262a41e2717e3ca3db1f002201382fef8fcf23a4c8512ae31a7ef5ea8dc907982077d0acda9a6fc9c40226a68012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac240c232f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001e9029e9bef2a9f679317c184508d527ea65d860bff83a7f1567b2d13d32f0fe3010000006b483045022100f0074a122477ed5fc836f0888ef13d0e279108935e193efb679db4f8238f3d040220453d855352a0c87f5f289c4b5f1f8c18ec78cfebe23a38b292ac38d1843ecd56012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac9c81212f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001214c9d58059907549385e50e857ce8bf98dfec4affcfb7797928d65cc61fd00b010000006b48304502210099d2783cce6ca7bd274dbd9c285aee39eac0aef83a173ce678614966e60b2c5102201b7e8972bd6c5809932ae9d8364329664193c725b4c9cb19cfd15578790c1011012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac14f71f2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000016636303654fa957c8cc41113d2f6ddf207c8bc2dae472e401d56bb453f82cc5b010000006a47304402200d9d64bba64f3794763cfe8fc686b056d6a9ceed0ff32bbb0a2a575d069474df02205d12275dea0cef1102b63c3f2d1aaff77b71f069ad76a03e9716c1cdaef80ad7012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac8c6c1e2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001b336e195e2e2d60c2937be9e8866c6c17d4b00b321a2d01eb5d38d8c70f5a20f000000006b483045022100feb2fad78a19dc49ca5a37f2992db6d62d5ae8fec2df4d567f08052f1a2fea0102202d07d39cdd6e7358aa7262fb2e2fc47f312612d499a4850ada33a3a5275ffe6b012103cded8d762114c2ba3bacd935161677919f88aab7574422559c1460f0e8bf4fd3ffffffff02801a0600000000001976a9141aec471f73354accefe4517baf02f8eb18baf19888ac61031901000000001976a91409fbde87458a54b4f1e02e7f786b30d09aff7cca88ac000000000100000001ef335af452ab058e16ae273e1c94ec94381d0e3c042df881f2c8068a81c6a79d010000006b4830450221009f707ed566c945c2fba42d1672800612ae4fed393671899c0a02e7ac1463696d022062341d768a8b685283bcef4c038c1cb317b2ad7255196301f96eedd74654a7eb012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac04e21c2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001d924a86ccc0615db0d36a5b13061be94bb44532cabdbaf55e1cbc262c4d30833000000006b4830450221009d7079a70bd68b997062d9e3298e56e359a7c20f5989d2c67ac4fdd9275eb9b202207a3b0df358cef55d2143404ae8f0ac2b27fcdea77e0826e3f8ed8c5341fd996a012103b7bae75c9f1f3af893af6fa24955563ce9cff394addfa749c8a426278f58047effffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac7090a108000000001976a9149ed260ff140ad90e446578983f9aa6d2ee99071288ac000000000100000001877634f7f4aea1adffed4763f5f93671dbfa75caaf78c01b8acc3521698a229d010000006b483045022100b16361778535e14ab13b7a1085d1956cbe9ad18217b2b91bd1de85545feec0c7022008123cfc1bf4ece21bd7a96cc03941a070cc5a9cc31d32e3358b8c4c9a370a630121027d6224cc190ba464c2728b60af7b165f822c81fd23f2f6cd3be435effaf461f3ffffffff02081cf702000000001976a914fafe462cab257c4086e1ea528c64be25a62799de88ace8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488ac000000000100000001c3505cbc77af0b70bd08673e973fc50195f849a9c0f847d6b472ad5a30739d59010000006b483045022100ea0317aa845da5c6998fdb562b26e45a102b2299c7a193bcb83e2f11e59cf5ac022077704dd7109dffef850f9f4a66d367fa87a951b72106a1e8f1ca1dd257d0ac46012102cd2d140a34af8034dca9a429162c8a0da28ce458bd48ce0e36a2d0ee549611a3ffffffff02e8030000000000001976a914e79754741e74f19f396f3f62d4cb05e26f2ed14488acdc001801000000001976a9148e028cdc3cf4c5567406035cb4b1208a0e9c67ba88ac000000000100000001b8766c0434f3203cc31e1203d737ef82d14ffe2a28bb07ed5b6c078b2d4178ad000000006b483045022100d8879d1e099f7c709c827049bc7389f0603f76649eba2147a127fe346e6f0926022072e69c00e21d5b492624af8c670c82cb835b8227116f8de9746c5c09cedb7b040121020d6279e599d55a07cd33c072b8a415e5d81370d6d2bc94638dad6990eb2dc919ffffffff02204e0000000000001976a914db9f1844eed67b003919af3531cbfe9fb62c861388ac6f8f2701000000001976a914dfe5f2f4bf52d76b7c8e8da48cf47c13ae15330a88ac00000000010000000546767e7edf775da35ee8bbbedf848ffbb5ee230f62a4c67275153f915e7fb68b000000006b483045022100d3afd2288293a93907d72bb5f77daab14e55bca904c87147f1c00301438ca7d902207d48bb4309288e186f77873b9b96e2f9fe5b48b6513e9b7f4ab7a29c2d901a1d01210228652d5618dd08f8568ae665beb2aa96354e4055d78d3e523a375aeebacacde8ffffffff2ca4455c159c593b22419bceddc9c77dc914f16d2b5f920f99b9c3d6a8939b93010000006a473044022065ed6b3b613d786a7fa205035e7ea911596542a24989ffb1eb6f4de356436976022009da8700e764cecea8e098e1a401df28a07ae3535578be32dbb28eed704904880121032b0ad31a31dc94d1e45fd4f92303f4e5cb49a4d7c5542112285a4985bdeff5d9ffffffff88f03e53539882110f03bccf2da93365a9665fc06e89e3daca080a0de57aaac4010000006a47304402204aaabf9eb9c3cb527d5f6d07ef07ef3318c886f35222ac78bd117ecc8c5950d802202d7041ed42248e500105b91ec289f5c8f0028c85627bfd2a6818ac77b8c33685012103fdc8aecf783092a833b36944136abc343d3f52aa7944fa0fba76726b8e9b9b52ffffffff027668dd2cea72366fff4d29740ad4902224b7d1a2da33ea9eea9c9c6158c067010000006a47304402205717e0928c51fbdf4e4ef435c3a1f2563246bff339271f5e2b30543b6c515d0b022020d22a8b3ee46f0e0381220c76b5fd6e9ec91e89bc590c512080ba1c784e1703012103e67a6275e8b0ae1e78dc772e2f3148ea08314ee3c4093168d16a5e6f892981b9ffffffff68bf1098a035f52e7ed6ee2afcf986345c67e9a5169bccb8bebff4c933e213c4000000006b483045022100c7e79440efe3d837010fb500b584f32bc20b584399fbd7b973e4325a4beb455002201d9598bca1d0985d38adc80aa9f8674bfc8edf8ea048ee03b1abca7ab069e8830121025d852ff1aca02f169d3af76cf74c2bdaeccdc24148bd7a48752deaefe63029d0ffffffff02c6b25900000000001976a91433d0d5b5b9e935a5a6c4b7a2c2f7a50e07bda0ca88ac56493d00000000001976a9149b9553da29123d2c4dfe41ee69a14203e713372e88ac000000000b1109075c0200000b1109079f08010000000020d27652d3c0ee533313c5f8d03de810a02f9b80744dea70784319740300000000714c71b8ae413b574ce20fe42bf9a946eb87400e1714f04b3edc6520a6c71063f6f2ab5bffff001d3f397769c3010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff23030b951500fef6f2ab5bfe21690e000963676d696e6572343208000000000000000000ffffffff023a89df04000000001976a9149c3e482a48cf85c14d5fa94d57ed7847fae399df88ac0000000000000000266a24aa21a9ed8f2cdc5acdef8195d2f391bcd2be5b82b7faa4bcd6cafddfe9ce20c01794f5520120000000000000000000000000000000000000000000000000000000000000000000000000010000000103f95f4b213731f9a2ea51c6f2b588ca7aeaf899606961347bd896fbe9b94cf7010000006b483045022100facefa1f3a8a34545361f230beddb78d12138d2476e45741730a08c8e7d8f77402201a448790f7f11a09af37bc13debfa17c275cc085150a843a4fa890ba1ba1af9601210239ab48c67a0082c1d130dd0cd29b79dae26cdebac5c95251c2a2988db6c1af88ffffffff02804a5d05000000001976a914dce1a1e63242038ec4ce2731a4286725b3e28f1088acc81f9500000000001976a91482a2d7ff2fd9685a25e7cf36ab790a7673fe294988ac000000000200000001b9c58dfbc1f11252d560dbbcc0225cdc2129f0a85ba316f9b412e4ec781c672a000000006a473044022045ced37906990521a56177808ea02305ae21e4b419796f9e63f6e2766cb4feac02207719431b02ef414ca13a628ba87adbb2d6ec402c7d7371052814f998e163bf8d0121028e6584bc2ca2b3fec1a3038242981b368315076ddd010593ffcd4ec58e42ed82feffffff02e7ad0c00000000001976a914ae379636387be437e5b483bceaa56ab3c5a0bb9888accde93d000000000017a914f3774bdb07f874a828b4dd2fc3d5fdc9f9777b7f870a951500020000000130035479792130d65ddc57297bda8666531443d5f2379d87095d71df9a3e2352000000006a4730440220541795c8581e40fd9b3f29f2e4806d72396d7f8e63e7ea39b6688079254c3f6802200c6d3ce7db6ef80fbaf5ec7c2fd5a1ef5503f9e35bde7b6625247c72d4e745350121028e6584bc2ca2b3fec1a3038242981b368315076ddd010593ffcd4ec58e42ed82feffffff02865488040000000017a91472157a98dbfc5dad8c8f7adf7d7992089c37ccb787b4092600000000001976a9144d49977f45bd77fca8fa1acd0ee9c34eaa24229888ac0a951500020000000001011d4dcfa12a80592cbda1e00c99d59bbcecbba8687850612bee2335cd7940e5d20000000017160014d5caa5fc8dec92925023e769c67d3b6b9e7878e6feffffff02a2060000000000001976a9148538ca19662b10012225a2da7e3e9d60229ee26b88acc40587040000000017a9145151936472641e19281ec125cd9c94f3094072ef8702473044022056afa1b3e7ac2ea662090d7bbe3164cb6957a484d82a30b95968b5a6d368a8a8022062ad3482b36cb433ae125f50f9686522ee0b97a97442737fe800f7d035290306012102a6428ca5fc0642298d3718fea2e0649b16d9d9eae4937077a47667621c62afa50a95150002000000000101f119846542790f98c235972484045e0620891bc5742a95d7e0293de44ff4bf990100000017160014fcdd72c902f3169956fb7f3f69bb0a49e11deffdfeffffff027bef2500000000001976a914fc375cc7c3dafede0f33a18e059b2566d179f08c88ac29ce5f040000000017a914e65f241b1b52ca43cb30e3dd376cd342c6c92a69870247304402206e34f2f11e3963b922ac152f1a339006fec3d8d212de644d4e2b3f867ab8866702204c2daef02ded96accd5ac11ae2fa324020937530efb4cf46eb54ff308e16fba0012102fe7c59d7ebe6c71f833d6a9563433d1815cd448bdf05d58b7188b7861a43e98a0a9515000200000000010168f09c6f4010aabc584a0b5ebe08d3fb83b66d19a39d9368593bce67d147863e01000000171600149751208fe6af4bdd69b78e648a454884febfb569feffffff02b73b45040000000017a914141ec5064ec26c099d35a5077af183ffeedaa2ca87524a1900000000001976a9147f180d90a6009d3665299c066f353950b788415988ac024730440220212af11c03aaa68e1d4e525384342d8ff041d35f347fe11781048f7532aa0e6302203376182dab9126307891117eeff11a8a82e49eea9c4b5175cabf2a460db62269012103e8e63f25d049919220eb03de8da68c5a697c0c01619762041f7594c18ff912230a951500020000000001014a2e2351d0c84e2f9cf4a3f1fa8d4526a65cd14a2c6341a9454f49493e94a54600000000171600146250f080902fe7b67c817db21e578305fbbfa7effeffffff0229a50c00000000001976a91491fe2e8afae299fbd94d00a93ec588812f314ad888ac6e4e37040000000017a914e8e1be0d58ba6cfa16cdbfc3a99f0f2e4149f0c9870247304402201fd6773e44b66840ada740ccf3d6cc3aac528b98f2cd1eb7e3e1b0afb7a91cf302205e204a39cc91e5eebe4f3ccd4135d31d79e299697e4ca4c41ba1acfc79df735d012103dd36d4a42ab694506d9529217a207f34ac1eb7353525a41e0e56f09f6607972a0a9515000200000000010111f47689eb5f5b975303aa13223eb670e27a36b9904b98fcaa2de6a1b17a4bf60100000017160014f048c9fc0fcd1ae79697c32671282b9dc625578bfeffffff0216eb15000000000017a914e34ae8e33f44578d20e5bace64c1ce2032df151d87a2060000000000001976a914530bfd93be7d2f5d2c828277a151392d4f444a2c88ac0247304402202f180da8c7d98f0848f8611865a40dc0432c5d432fabb597e7679477185e8af702201072e0296db094a29a2e06a6531e848dd23607160301151d250cb1f5ebd5d97c01210309bb7c7b197cfecd7cf04e882998230b465951ea02d66a9f0f275dd615fe045b0a951500020000000001015084fbdcf64464c3a899295031aed8cd7cc337c6f4549f198b7d891d8e9e81140000000017160014a9e76aa9db88a4a448a2d45c3750ba1c8e47facbfeffffff02a2060000000000001976a914b0cd5145b588b029097e4e67d789cf52047b90b088ac549c14000000000017a914c537a60f2cb15e648a2bd7a6fa84e75e30db031d870247304402205b6d97536a92a694f29ecc233d6eb3d896fcbe5d935d2308b7a198eb22864c560220029dfeaefcd37bfd2242329fd560b70cc399da4e992b2548670b1c30ae20432b0121039125b5138c63339ab7727c49c81572beebe1257f24b30023208b87da455843a60a95150002000000000101468ce63d2f8ac6263dda990903964f43f7c58d0c7151f9153508516654d7f8fc0100000017160014894b9e68d4cdcf68f646e848f0e9d9c80899f38bfeffffff02924d13000000000017a91497bae54d4608e2cc2f61c16e10df8aaa13253a6587a2060000000000001976a914d1797f98c923fb6a16499a3c757a6c5e1724fbbb88ac024730440220795806f5129bed04e3c6090ecc4ffeadbc089b40c9ffd597ff4569f739797f50022027a70b9fbaf060021dede876fb26db656e429e7058343bb074e343ec66ec29270121038d7895fdee6b984d2b49410ea0e0918664df9e1ffb65c1f767b82058be8753330a95150002000000000103ea4ee02fcc0a7adef1e952d2e8c5779f13e757d69cbadc48424d5710283c73530100000017160014b6a286a872591dcb819d18e106778160f99e85dcfeffffff36938b1e0c0919d2052edef8b96f1db63eb758ade51359fa4be9b70525810ffe00000000171600144cfdf006d42b5a865306152b28158598f7c2be8afeffffff1ecc2ad3791fabefb78bbaf28265d20629ebf048c2c4bbe4e18c816cd441faa2000000001716001443df5b8c8f41a90a125dee484475fdcb5ad3417cfeffffff028b2a0e000000000017a914956e8bd30217f4e79aa252770b1c10c6b643981f87cd5b1900000000001976a914f4433c4d95c78166188f801ad7344473bbb7d40e88ac0247304402201856630c1bd17ad9b5c22fb8f9ee4c8eb5e19667e11ced92aa9f8bbb735a031c02201ab63ef272db05fead5d850370c61fb82b0b7d1936dfd0e8195bb987557cc70d0121039d7a4d5f8cd8a7db5b3d09be464dbefcf19ef4a76e943f9ded1397529980188d024730440220342fbb46778767ce588483a2bf5857068899746f3bc4549911295c73071dde0402201b1ee04062353573821d6a36935f7e2ae5951b6b1efa73c14f4af27e17bbcced01210274adce962929a8224b3bcc275df18a9296e6e3f619612f5d7c5016fe12caa8280247304402205b3b8066a16009c2b83a98330de0efd19b5b4362ffee579e1c86f0c83027802202203bc6dd62c77450c36e418736bf68c4668f3451cd84ab2e5f0a40abb607d55a9e01210277dcd15c3afbf485f4ed16cafeb8c485112e8d5628592cb1989c280a8866962a0a951500020000000107f2ee2a3766e4229adb172014b1906a9934c0dd01df7e23c98964022be026b7000000006a4730440220719fde97536173ff4002785eb7b70103a303a0e6f4f36beaf4893b9c441f8a4902207e5907d6fc3be4bb745cc6c17462a23e47d9318261fa2d0854f8bec41c2f72b40121028e6584bc2ca2b3fec1a3038242981b368315076ddd010593ffcd4ec58e42ed82feffffff029bb73200000000001976a914dd439b36a2ea793371955c6e09950d4e5a03368288acb0a78d000000000017a914a833fa96c806effee1430e4674ea52eaa8db02b287f694150002000000000101dacaf34de18e1986d0721974ba3f2c5f0066a415826cfffe203252acb666512301000000171600146d7b504a264a822bb0b8046e537ffa48be5b10d4feffffff02fd5271000000000017a914fcb13fc993c51bad80f1e243024f866671ac24728781653f00000000001976a914dbda86ceede8ad0c395f035ba37ac048f6707c3d88ac0247304402202b26c6129a9b859efac61eb95cd92faeacd37e47e877c5f42b73645d4adf4bdd0220305480c63f4ef41e9872ae61d95d8e07339fa6beec42105122fc8cf80aca44a3012102ad31e7241c0495cd63ce56a92f45e29678264dd3229d38ebd84a9f384f10d57f0a95150002000000000104e8914bd5c8b3dd9133e02e37ce0f9bb5f424becadf6b706fc327e228c3e1a32700000000171600141e376df1aa76a03ab06a7e85a7c4e1d1939ee896feffffffe44cad1fe71b73a83673a0d8ce12f9275f415b7cc950e1a69ae21535f648c48a010000001716001401ef0561e89df290c7b1b282d9fdaf3cf70020f0feffffffc093aa8548541d5b99bad69b38313e892c793a7ff8afbe6a817977ba2c94e4fd000000001716001451ad0247e73d7c1f057a29b2c94b1b0a03befbe9feffffff07f4fda74c42c5681660cf63e42870ceafd021f087a2136bc2a6a9c8a2c455850100000017160014740bc28a4943ebe92f833a2c5a77759909de9225feffffff02a58616000000000017a91433a910b6d25e5509b1a79d5638ed8a9b0455ef7f87002d3101000000001976a91403fd292889f562f6ce14efb05e82db3dc548382688ac02473044022054de99b0b7cf0d1815e9b428eb19dd31ed4667a1ba152f2adb9ea2e87757a50202202b7868a4d758674f37da85499b04ec63f6fa5d25f6f9a8ffbd5b9f2033482ff4012102362d0faae68417dae125d3bf85af8766eb8416507e2c8748032b7380d2b4821102473044022001739c24a2e5b19ea45ff2e34f40e79c7a9dda4c14d7dc9d1b9fe757264b3dec022017a29e9eead271136dcec08f6aa70672ce3997e4ea28db70eb05669ab8cb34d20121020259f4de8f0d2da30391ec837f4305084c15420abc56d8fa03f57b11a47accfa0247304402200887a831cddbc27ee4ace65e0077a742b356b0bedbc183aab2ab0d50732e2c600220378f0dbb06e4db8078c75eec06c5123dbd9e8e8186d86454ee5ab76adeb970a201210387a86c57402cff7772e2e9dcbb15f71b679dd06e24f54b115be66fe2d6fe8b86024730440220324aa988bf6b82bd08fb281f6f4f5355c01e5ee0b820edaaf6b4f1a9fbd121090220396962b68719067a79042bfd292dc31481870d05a168cd004aa372feda5031ba012102d4344cd4424096d5154ac5181b1741ae0f617566fa7acde0390edc7401c373070a9515000200000000010150608a7754837b3ced3dfa5cff7c2b8625daba9cb235f3acb946f39e2e82581700000000171600147fb0d58c9a24d13be2712744fdb572690534f6a1feffffff026f870200000000001976a91489e08442f34c1cab7322449e56933b0c8b78f79688ac037e0f000000000017a9145177a11e8b60afd4ca82ed555ec223f7bbda4fa88702473044022026e01806a5ce002cc8a6006948bd4b64a4748ce98212cab167825b4f72afdf79022023d71461bccc07eec369a461c45565d3e795a36cf8aa630ef8a70188172cef73012102e75b6870161ce6ef1837cb82920095881a50ef864586b0bb00776932e648afe70a95150002000000000101b5c4b9ea4f1b49ce4d2165be6b6c86baa7888fbf7042126c13df5a19f7365f4f0100000017160014df224130c1ccd715f5922f0a9daaa701ba697f0cfeffffff026a2f0e000000000017a9147ec74ba06f5524dc4fe20d8a2b310ce5bdfa11148779060000000000001976a9147fe07e70477adba7af572c510d1fb0e112984ae588ac024730440220776820134d7ac3fab84547d56f874f0622ff1eca2261f211ba1bd2dfb7df335c02206ea646a6f009caa0165fa136cab7b4cfb7d404aa99b5ecab89567c8ecc0e9dba012103cc9e88693a824c259f8b81f371afe1f3a21836b35b8f97f05950f7cebd21d3ec0a9515000100000001f278bae9c0c595f2c9006a598c6d823e0e354c518e07b2ebb60574e22c2f4140010000006a473044022002bbeea93ccb732148435093f20e93b8ccef33db4bb4d267c164f58a0624d9e8022041f78fccad3c66b2f4f6c3d06dd2a8a2f69f6176c1af004e183082ab69f61833012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a91438e4a66db3089a4fbb814ded136c923f3bebc12b88ac72042509000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac000000000100000001e887fa046b6605cabadcc752222302d3394af629b36ebc1673d3e479cec3953f010000006b483045022100f6918bfd6543a86360d020cabfa6ef5bcd4899180ebd22092f8907285be3417502207b652f2ef0e8c00827d3fed298d844ffdf58428ee62a87f735ed8032ddbaed0b012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a9142c1c151b9c8b29340190dc6fd877015c14c47bfb88ac22412409000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac00000000010000000141731e44f86118c895a828551adad0fe5d26afec661b405cbc2866cea125a609010000006a473044022017e7eb1d3001ea99354d1fb148d8de141b2f20ad845e6dd9cf9dd4eb6212775c0220787e5831721b7fa3541b183fc510550a331a60300dbe8261886b275dbbfeff51012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a91438e4a66db3089a4fbb814ded136c923f3bebc12b88acd27d2309000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac000000000100000001d6e0db9399f730473d2403f6ccbab0cfa0c4a71dcfeac8ea72961371a1867f77010000006b483045022100f357c43827378ce3a32a38e331f6c9463f3a6648e672fa2eb97a05072c6b5e5302200c58e2a1ada9ad16041a23750b1dac263076724e0abddfba91cb9fa148ff3c11012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a91438e4a66db3089a4fbb814ded136c923f3bebc12b88ac82ba2209000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac00000000010000000134ebb11f2fe2395a8dd79d60b095c4f95de5d761fa8b423b48180b472a7a67d8010000006a4730440220648781aede19ecf206a420fa15b2dcb32b1111a41231c63de67d7200bfbb4b9d02206965b3207bf94c7c2676aa1e43f5e37d93fab65c2273e2e603ac2eb0d49fe9f9012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a91438e4a66db3089a4fbb814ded136c923f3bebc12b88ac32f72109000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac00000000010000000147fe3268ec5a7a3ae6a80c6c58c06682d0e16955f5b1ab1d54cdf1111e7817cd010000006a47304402201809a51a19fa0332f7c946f930e15fdd77b75df48997102536eeea27ae8b78020220573db66b4e9f4202841652ba9f434973ade3b62f15fc245f9bc8d6cc7a9dc9a2012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a91438e4a66db3089a4fbb814ded136c923f3bebc12b88ace2332109000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac0000000001000000011add468373d0d6af5f3b80ccd8a9e653927b0db1857493af0593ea8a42c8cea6010000006a473044022055b05d612e8ad9506d318dfe695c96d30c2433eec49ba96cad81bedc4eb4ed5002205c89c9a2c189f1d738fd10e48a76c9c9c4b4f2936ffeef845f53f140441f45c7012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a91438e4a66db3089a4fbb814ded136c923f3bebc12b88ac92702009000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac000000000100000001cf6ad2d44f3723b74421d1ad22fe0b06b78d978b087f91b64aefc0402df999a4010000006b483045022100ee0c18f2081c5a4972bf38672ea0055b6bb588c2792f3fa6c9432306f033bf7f02203246ad644b556ece89746da0b610124027c1caa5c4ff1fab7f734cc1c849e1a9012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a9142c1c151b9c8b29340190dc6fd877015c14c47bfb88ac42ad1f09000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac0000000001000000013c1935d029d695efa804189127fc53c4b4a8e9d2c69085ee551b642e4a1b159d010000006b483045022100ede66ebda985e4805f7be87a0a8e2192bce8f714b315bf10d857e132c783396b02200daecd967a065268a6cea5bf9d16d09f032ee857a479cb6d50ab59f1be353944012103465042afaad3b2b8a71fad63745842dd3561ead9cb92b5331f3ec0857331f2ffffffffff0200000000000000001976a91438e4a66db3089a4fbb814ded136c923f3bebc12b88acf2e91e09000000001976a9148cf4bab7ad7701bf40401a9b5cb25fdfb0f5a99b88ac000000000100000002b93a29af63081aaf2753bf6e072e87d512bbd4bb645555b7a8903ce0f21a0685010000006a47304402205d650af7502b8a3a9814ee68f5a76b8dab3f12653112b27a4aafc0f669f7e76a0220144d78cf145155633a15b5350959b6ddaa733fd3da53c5d1d17b809594acbe9001210334d417a3ab3d752d142bffca7e41dc909e461607d2746cae9b1f5460e0c93833ffffffff809c57fb0827e9b2615a0f1936ff18529ec0f8891f7dd7d3e1e3395313efbe9b000000006a47304402202cad7b10e5f01c30dc0811648adb27c71c9f4a8788a02dd3d674f0c0f94847e6022057c418ee3c876742b08ed32f96e89ed90b12750c44381815fa1617906ba145a4012103ae35e1cc51518d0e9b13eee514d68cf0c3311ee524350ef9e36aedddd81f9a7fffffffff030000000000000000226a203966636336373631653438633436613362303566303463373262636438306363f4e20000000000001976a9143130ee08abd33b23048f4166c02c464ff84f176b88aca0860100000000001976a9143130ee08abd33b23048f4166c02c464ff84f176b88ac0000000001000000026cdf0897170bed1ca7609bb662236aa223375ed4d92b3bdfd2f7fc8f4a2394a2000000006a47304402205fd9921c90cae392231901924f9cde9a358efd432d5218fbee7df9cd6cad7d0e022043782d2126f20b209a181b050500156432840cceeddf36bd4fba488917ba8d72012103b9966dda9cf70870800b9fb5d413f55b3e90177eac55c96b9b9f0314639b270effffffffa8dee2196915e3042ab3ed7b5696a5729da53cea6d74d4fe42ef6c54b464e8b1010000006a47304402206d59e6c500895e48fd8b33e6c1f133e8458f928f09d88b1f3dc2d992436966f6022001c28b847dab1c8b3ba855143fc4afec1cf02aaae5248f8896c9f5ee3110f18201210243113b39c48bda8b3aa3033f07dd9892b5b01ae1d01bde8e17da753f23f540caffffffff03a0860100000000001976a914cee3d2db2f866ecf80068d5c71b80b1037c27bba88acf4e20000000000001976a914cee3d2db2f866ecf80068d5c71b80b1037c27bba88ac0000000000000000226a20396663633637363165343863343661336230356630346337326263643830636300000000010000000cbb14aa63cd40efbf33a54a9b219cdf9d997d34ea269c31fcc2c92a6ed4881f1f000000006a47304402203842611183f443cf216cbcf325b3a175648185855b0f42c6cbd0e8e916ea3f9e02201b0ad0c39dca32ea8147ab511d2202ef55dfcbd81e9df83293b18ba2dcefeff001210282358e443b534b8645c3ab090df06f14cf439667ff6c10d80a06c390832486d0ffffffffc5b1b76b077465d99f45425a916433ca11df30155337a0ebc90035da4c5c6eab020000006b483045022100945f2588b72779fa80266e3485258c9a4526f1a5a3518b40865fb43cd32b2e980220068d8e7eb41d58d2bbc6753b21b2d98bc8230cfe13d89fbd32af1fab7b66e10e0121038f25115a289de85feb2bb17b289785a11f818f6539a78d7907eb84b57f346563ffffffff377fe2d74ca25e7d8ca4d2c251ee3f6249b986a8f4e4265780c5b28ae2f5cfe7020000006a4730440220689f354dba9d3f2e7dd4b666ee5f356d9ecaa76eb6ceec2d7e5fefeb98b4279702201ef592f8c2595e2f2f4daca6a952ca8b2bf7bc1a26b6b33553b6613314751325012102ce2f2ba4cfd521d08494380e1d9d1e68ebbc6dffb3547dfd84ce06cd98d09e27ffffffffdbae9b12a6f97145ddea43d4b80873127c716f232ed38f14ce5166c80dc9c969000000006b483045022100f16a44c9dbe9eb2721f630e92348bdc2874d52b7a3e8f9acedba56c17ad03fbd022040b2d7f15ca0cae1bd406ef3e0ca4b09771ba43c4d4292206ad450b0bac87c86012103870113db59cf3e1dc23318143690ae571b48cd3c86f38370535a51e243afe341ffffffff09397160b182bb7a31a4476297e0fd444fe6f77814424dca5852e566863cb68d010000006b483045022100e0d48eaff959d9078d514f97ae5eb1b817be78f0556179b5c0e5de4a9dcd4e2e022015bc945f799b5baa7fc5617e21ef3f2e212398192714bde227d9d15ae76106ed012102ba4f51eefb8a01bff23e799fdc2e2b1cf3d1688a636fa09c1653b20c5f2d8f58ffffffff4ae9dbbbecf9603493e837bb9855ef71dd7e7cd3b0d0c1c94f7d2f8432f7bd72020000006b4830450221008f8135befe88937f82389bb76663b8aed9a5533e3bfd3861810543f624f6d661022029a2785a92ceb453da82acbf9ccf5a4eef3dd4d0e38302983e255df82a5f5a3301210292a88a8e28ade49158e143d9be76ab2c7f3cf3686703c91a925c23b87dc4127fffffffff6036551726d9a3082b62b1202ed17feea043b5e80182043e50c1c1a089b5a763000000006b483045022100d0f4a5ae9a5e8a8bd34492a68865df092560cd4238bc5fa60101548212380e5702205fc16c294e8fce59df65376390ab87ec4c18dd92beb7eac4bd7e7b1dc89053890121031e345270ce6dfc33f981e6160538a12a9858cb1b1d212c7eef5c12d7a2f6081dffffffff25a5c0282b80501ec578b2d043a30e9cf847a5e1fee8b99342279130ca15e266000000006a47304402203da49e67db16021d23b9f76b73a921327504ea87795ce67778284050ed6e3ce302200de8a501a69d4c4a2dd19cc3ac3808c75a9147cebc8281ad1d4c678b5cb6eaf6012103db2aceff50475742186f4926c3e2a36a0cdf8d3d747bb98db0f02ed6870a1bdbffffffff20a16d6e8101f5a26d29f9070f00e249139f3c796e5dc4ee0daef87705a7ff1b000000006b48304502210098f67816f5deb2d805e1f424d55ff7a9ab3fd816054cfc9c9dd00673029e20c10220274c4be29c89fd7542634f9be617349f6b676f864b6d31f337a452c0d7a1650f0121033508a064340cf94ef8a2e93670b537871aa183f549b75c3589468e6a58f77836ffffffff90ba7ddb54e5a75269f952c03413dab0c29616a522a5f7b8e9e4bac4f40bdb41020000006a47304402204a7ee52e200767dc274d81c22b274c2e088c589876944dba9c41c60b0d8eda04022013cf664e3c6933be77c4654e686f03a69b5d14c52723f9ba95f7c0c79393b4190121033cc79c3c87f9d004be9a8fe50a41796d17731d59dddb25edf6d320635625bb63ffffffff08319a5a5b38c4fe6f0fdf9182296314664333176e214b42641d8415192123c6010000006a47304402204b00c9ea8dfc68146cc75148775ee5848a9a8bb6e57d519d02f5dd0088acd6d7022055cfdc7edb72c890e67b58715d6185064504ca41f3536928f5a05143cc8756800121037224f03c860fe1802f2044a52268926e975a6e1519856a53ccfddfe7b05dccbeffffffff3292d807057c90de5f6c7809f22185a3e3239cd1defe7206deac4565147d154d010000006a47304402204b3cb5bbe048eee18a4ea7b744ec7538b2b07e146c986622f33cd3ace47f47b602207372854335141ab2b1545bdd8a7f68a2e0942da2b7a6ff8a1036b84ed097050b0121038e84cfa1a767a7345390aeaf7e558af67fca811dbdab91fba3e5dff7d4a4342fffffffff03a0860100000000001976a914315477871f5ca925f0491f04410e0a46a0d70c1e88ac0000000000000000226a20396663633637363165343863343661336230356630346337326263643830636368100000000000001976a914315477871f5ca925f0491f04410e0a46a0d70c1e88ac00000000010000000162c46d3bff56156802e078beb6a07a43d0b24042c7d18ae8dd3d431736c767fd010000006a47304402207253bacafcb4d9e4c7039bb0a118900e950c6cfbfe9809d2c59b6d7cadab83f902206c55789a75f3f2e7c7d04e5183c1d84bf6d6189ee2c3410209a58ba80a2812d4012103010a6f5386d9029e8f4aee949020c8f489b8103d73246f37b40dea965e99d890ffffffff03d8993800000000001976a914d9ce17e74385770cd414c23b8673733fb0d018db88aca0860100000000001976a914d9ce17e74385770cd414c23b8673733fb0d018db88ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363000000000100000002533cd3160167736af1b8238d1e4ac87bf847bdbeb711c93fced4b6194a231d4e020000006a47304402207ec6818c0f9d05aa1252e10f81a67aaed92fe3150c09a25efb88d7985ecbc1f4022076bfeba18ca8c813fe374117166ec78ca052d4f3d6d74d09453e288a8949e1e2012102f4cf97ae146c27042c81242f16e6094225ba90b5e4eeb95de0c282572ef73304ffffffffa911afa18a13a12b2a36a1524dcdbefdb6d91dd9d37b86fe45113a7ec313a85c000000006b483045022100e42dc52479a38b167a61793a7cff6eeccdd5909f4d1daf46c1a5fb1fbdf6413d0220349b9f2be9792b164728a5109629d07bd0ddc4125e2b02dd47e526f096422ac2012102d801d444f09519e2de5e188bd4a9bcdf77e6e00703e5a29870e31549bcfce11bffffffff03a0860100000000001976a91438e7a740c444c16df0a9dbd6d1672be208b2eb5688acf4e20000000000001976a91438e7a740c444c16df0a9dbd6d1672be208b2eb5688ac0000000000000000226a20396663633637363165343863343661336230356630346337326263643830636300000000010000000242ee63b1ca88571dd3be4f177501bf49a52dce922155711b3f2bf9cc109d51e8000000006a47304402200409340c50a5dc257b1a08e54aa30013e8df6cce42e59057fa1ffe7ca5dd3aed022059ff03384156a33a8e9fff52f43ee4d64f566c3f4c6e8d46a7bbb8353fa2b5680121023c716e17d2e4e0c51ede1665b206ece6825bc2ab443ee707f3473a51e8950589ffffffff98a52f0826dcf53bf5da1285afe0d8e87eb301ceaed523cea04cbc011758deed000000006b483045022100a328c174603a568a214b51659d49f8408ec709d6808e972886d597b600238eec0220261985449508f1073a8f14fc92f755f4fada062152f75afea47267f049512446012103f95e0646e4c4788686db4847112f297f9e06d98f60f06fa40e6019ea52e6a702ffffffff03a0860100000000001976a91459ffd6bf212e70162599b80723a5a35b242940f588ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363f4e20000000000001976a91459ffd6bf212e70162599b80723a5a35b242940f588ac0000000001000000023e8aa28e27ba201b2d419610432ec1637237f9bd4f656f20ad53ad5eeb0c871f020000006a473044022062b13cca296dde89dc38288b5b102438a7f12d6c41c08f6eb2dd4c5aa31314c0022031dd064985107a0d7c94091d68a530b167a81b4325afbdb26111b285a3dac5400121039c9ae98854fd0a2a5e8bd0bf6e0a87bf13c06615db9e3090bb630d969b2a9da8ffffffff8b380dfcfdf8ee48c79943ef2a05ddf6244d5d4dffc0cee4507e0be61063d932010000006b4830450221009775dfb9f568169b65d9232be17d477958d9794a6be8b61bba70f79c7d8bd89302205d84784f261bc0b4b2cccc0e2dbe05e280d1c3073db68aa6ea1b2957c0fbd01b0121021e51358efc5cb1be6344dd8701f692f1a45b56b75b5665167503c52e6c5eb431ffffffff03a0860100000000001976a9143904f596c14d6fe2097b6cbaebc623e9cdab9dc988acf4e20000000000001976a9143904f596c14d6fe2097b6cbaebc623e9cdab9dc988ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363000000000100000002812e06a779506ef0878c88d210b582e93f78e67679bf856ba1f4b4639c5c3aa1000000006b4830450221009eb4e0cde4e599bc4677870c794e59c08aaafeb88fc9ad7b6f84f804a501aa43022023eab3c9182baa73d64db863eaba79027468a4d01484d2414502b4b63c16fc22012102f5b5eea54735dfc09a9f3118f2afdb77d20319c5842a85e5ba5b7c14270e4165ffffffffcba955e2a46e064b42eff77537f28ad9dbb5d222c8b3983026332dcbbad8d1dd010000006a47304402205b61a5d191c761c7db3bd4683613c821fc940de3fab584494be73075b47c573c02206107dc254a1bc92d0c36feb5529bc87f59d22f2bba0f6dfd8efd10ebcc1002f201210372ea994f3ee9e84d5b9027952b6b7196ff5c974e86241cb9d3c055626fe05561ffffffff03a0860100000000001976a91464a2ab2a6f9074cc471a17144948d7d449b4f39188ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363f4e20000000000001976a91464a2ab2a6f9074cc471a17144948d7d449b4f39188ac0000000002000000000101935d049d3f853c9865eee4693b0225ff1df38eab114b17c1ee622d8fbbc44a190000000017160014fdec28ba6b42bc9fe4a75bb271301da80e356596feffffff02c7d3d4000000000017a914434201006110f358c732618e8a267f11e18bc03487087c77136b00000017a9144608a7b97022103e74117e56ed201eb19ee1b2f687024730440220191293f26e66681b2465dce353839af815086b3e79fcb23018345f036016f15e0220688e3af1cc58269027c78a11a069c9673062b4a62416d48e6b000eedd23aa30601210338ca3337bce7db779c05918dba70957e6da86571df95dfb459a1b4ec4fcd48bd0895150002000000000101f0dd68d4a8c04308dcda195ad7ab45db26edaeae0bf557b633a1eea0da26a98d010000001716001476cee8ab134d69e3247286aa4d125197faa2eb44feffffff02e4eac7126b00000017a914badac7e4c7273fb6e28b29dd0e7361466d8d0a76870f4faf00000000001976a914a9a4f0d8929530c345f671bafb8cdcfa17fd9fad88ac0247304402202ab629e2ffcea16eb2fd3c388b0dd559a0ab0b84982bd4164e892f54ec55207d022078b807d43ba4124196dfb35a34b61a943bcfc3b06da606234f6691c8f01142a40121034b53212cfaa8dd2056ed62b37c8779b4b6d2b1804f9055c4298d36062501b6f60a95150002000000000101f755985c391f8c665c550fe7156880ddb1a46170dd81baf4d7594cf85b7aa5660000000017160014ce4c20607e281530ceb7dfc6ff649c42745e7feffeffffff02729d1d236f00000017a91418e78404fc5e1d52dfd2e824ee5e88aff051d9398731e4d8000000000017a914f86a63da2f1d1faac56918cc5b64938422d4ab30870247304402201258a527c12469902c8659f85a5468c2b82fa00e783838c6d1efd75ecfd33627022049f550cd5c0438faa712f609474165014504d8f594a70de86f782b35731399ed0121022a5ab0096713320deb829f1d7b182ce1f2514a336ba029a31b9da215936be852089515000200000000010191d557895cd34ddf5e3a99565e64f90664695d689a29f694559f5298aab2d4e50000000017160014ac4becd33c590ed046b5569e81645ed5268204a6feffffff023dccfa00000000001976a9147f5ab9a9234bfa28a8ec5ad80a1a6ebd5891d15488ac208f22226f00000017a9142b3c656a1695d48a10d2c1b8d6d9b339d7878f40870248304502210090f332ca5f8e7ebae747726aa468ce5b13b324cb15edee2584fb165ee1198df102206ae3349f9ed460194772cbb7d93af2297f053a62040d377f0bc0af24387b1fc60121021e63503f5af3cf4f0755db61c5299c678850d1cc3fffff999d03a46416cc4a6b0a9515000200000000010142ec13e0e80b0f47ff5e06b65c5f98f931483912498518a509b98e36d2b72ca2000000001716001482a0944c7cc800abc19d46eb3a19bec10d601b97feffffff0267530201000000001976a914dd87a6a04b0dfa2819d1a5c07f09940cf533d6dc88ac03fcb2ec6b00000017a914a48539d1029ed4243aec5d1d230d6110ebd3f806870248304502210093eee798ed4adde0098ef5428a3fc5caf5a68f0e955744f4933eea47a407d1b4022054f4a0eeffd664c937e6d66990decd64db81fefa720e5a6e5b1061dd6d676e67012103f9cb969fe9ecefead6beb753c4f02ea28d2cc40af39038e8bab9d4ed5f3efc2c089515000200000000010104e532d57aea508dd658ae375b9fbde5041f01ab21e01a749260bc5e13fdd09b0100000017160014fc97c81f3df12e23a2a115f818991fa686da10e0feffffff022a3ccb00000000001976a914d679f856b736243faa53126dca4b653943b61d3588acc47de7eb6b00000017a914907f78b72dc7c51e24935bf0b646d3152bc6483787024830450221008ba6ce0bcee29cafb2f771f774a7e471886021a2e04500353afeb70c8f3dd4a802204d2b5d6f8d649242e89dec1537619bcae3287d1849fd4ae9d78d42a258ac9db8012103886143e3a584f4038204ff4c2ad8f28e017dabdfb7f2979b4200790ab34a02d00a95150002000000000101e3786bc96af21e86067d818c3104a0c69c315b9534232b450892c7e3f9d1d9a701000000171600140830939d389a6bf6e772c9493860bb3b587740f8feffffff02e858911c6e00000017a9140106924c3bc193926ae1b52b01cd6962ff946e6687f651a0000000000017a91473747ffa85925ad690ca2200f4359abed656e9de870247304402207748d51a507206a0f7ca7f07eede665be5ab7ea34d77d3551f48d52a9a0539c90220357870451e9d33cfaa86ca3c28e9407241e94679fd8ffaf411866d4b1785a6fd0121027d585d51c39bf3d2578b1cbb5515505593c564d2fb73842b62e930e3e2752f1708951500020000000001010c207365695ca363a8f57875171dfb60bb93c3652f725b397cd336fd5ccdd6990000000017160014d7cb1736310b451bd04a6256672527d326c42950feffffff0287599fb26a00000017a91472eea6245579bfdf38fae2af7f60d9dfd1a3e1d787e14edf000000000017a914c69536772dea4a0fdbd0ab9e0cfa63224ee355e387024830450221009773e2020f6bbe7f450dcda5763843fe4581b38db1fe7d76d5f4da4b9c60a89402207cadd5cc14146bf85cef1f17bd87d77d60edc4144ae495c563948caf2d195be0012102f8dc2d83a265993060480107c9683b4446fe753b6e2c81b1ac2f5682e3d5747a08951500020000000001016c767bd2aebd19e8a6d71f2dbd747a13ff9ca721cc63556ee7d8fd4a606a27bf01000000171600149f46f0edbbac4e8e5c89e2473f15bf27e4913105feffffff020b114d050000000017a914c6953406175961a7beee4d935062ff639e4acc3b87f875950e7100000017a9144b4f8f248c088c3c82e111d13c9daec0f562cc3a8702483045022100d870f3c1c5a921f82a91658044210c1069fe7b03b3a5d7c689be1956261fc28a02204ee2d3fcc6125cde771bf8c05be732d92bcdd300260e9f6dd2c59fd79ac0693e012102c48f3ba0ceb4dee43dfe75b2c23b9fb8c6c7c478809d9f3ee107b4566a8d5b0e089515000200000000010108ef8d60384b5d168a32e7090ed2bfb50c897c18e52c68fa161f06478e8b7e370100000017160014b4bd8dd0e5e71d7190df3707b2c7e25c1b3935affeffffff02d7d0ef000000000017a914c69534f6b92a7168974b1d16535500f800d29d4087ba07b1c76c00000017a914296341e5e62650ebffa0524c30e2b18120bb869787024730440220594eb46e67b407c901424db679579dc0d33f0f280ee3c8cff5c1766ce751369502205bf6a499b817f13c26dc202ca316220c8fdaca3a3008401aec4326da8eb3a9a80121038b05451a46ab53d86c0ec3fbf0b9fb368f592b3886e25238da13e3c63aa5011b0895150002000000000101dfe68162f35c10df16bf5761bb70952606a44fdc53e5d87535bb4cdcc6fc24e80100000017160014b3e9f889b75c8b406a27edaa4b8571536ad6a4e6feffffff023cbaed000000000017a914c69534b2ae1d6cb9be5fb3dce8322ebab4f2858d87330cc3c66c00000017a91498322b802968b16ec75cc35da936543478c59c4587024830450221008832b55809df7f48b00c5da397e41d346e7c2657a94976d18ed1aec392babdde02206472aad386d56831e15a504d55537efa4647bc4dd93328b45633815655d74dba0121024d94b548cb964ea8929bb8f0d88f145c920fdd14cf6d810962b6a9bbe5d8a5320a951500020000000001015e7b6aa9de27bb7c9dc0aa3e0cca32a0f6c5f1721e41264446eccc61a0bc527501000000171600141e8c08ee1496f22b297ca218e5e83d4495edb532feffffff02f3d16baa5400000017a9147cbf30bd3f327577803d54eb7895dc8b55c8a3bc875db8e9000000000017a914c69533c2bd46c7bdcf35c9f43d57553bbeb291fd8702483045022100f283e881fb2d01dfcb090f47c0441bbef512cbaf75356fcf8f964e8a82f8029c02206ab6995b5497a9017e753c3655818285e1da8d5089be3c694f47a73bd38d7ff90121026f7aa75b53b4af1ec7cb04d8b5fafef1177593697e4e9a4d652139c4827a00f308951500020000000001012accdfc4ac07327fda863343c0cb1eb2b5ba1b5b08813e8359d1835f19debaf100000000171600141bbbc93a9ad22cc7dd14b4bfde8bff5d3138f84afeffffff02d0e7e4000000000017a91458a96c6aa945a728aa87940b760c81ec185d900a87cd2fac1b6e00000017a9141e317334ef3efbf77660ef3dd0285ee4b7b09cf487024730440220278aab6e09413f03e079ebf95ea05e0b712e163813fe685dc956191339d332990220130b20faf70cf213c0869861fa7bd155261ccbe673af394f2477f549e0c11d3f0121030a908a84da7830f6a8098dc5f1e496a766cf2da6d1747c2c3fe6e391249f2d440a95150002000000000101132cb2df1c85be6c5dc860f2244658b1212e8cd996db3b163b868ef06b149b5f0100000017160014370828352031ac5b5aa47b959439f7bbef5c247afeffffff028cb49f000000000017a9147e2a4f068200cbf4fc811d660c33b72daffc38a887a4a490a06f00000017a914799e54e9bea25d290f9f2b01265bd4dfedc77f9387024830450221008e3777a9e8fe695ee669d9c54e95619c19fdc323a8878632ba4c60a0ec6c342f02203192e07acfa3f4e1e6b2236d27ea5febd114bf71795a8624132f1fa1e5925f1e01210285790eec1fec12480642629b22d51812deb22580402db5a8d11cfb53d32249bf0895150002000000000101d740379223eed70a65d5c6df8beb1da3a43456e21a20412978a53a55501e5ec801000000171600147c24d653f68b2d5f58484eaa08cbfd8338308f9afeffffff02e0b8c8000000000017a91403b53d82c44042a577b0b4a507601514d0bfc7148779aac79f6f00000017a91428e99f50f1eeb5650f2dbfd93cb1cb1a796a3a048702483045022100978afc6a9c7ff72a295bb737cbbd9f533ee3d06cd661949959738c63f2a71c20022059e0b5715ee9e2341f268271785a84b1af35ef9d3631c3853b1ca1b8e7cc239d012102f94b5c5dd6fa9914b47971a1c213fb461b7f7418cb21744141847188a12b6a480a951500020000000001010ab19db37d7d3244adf1c4a75e121da3ebde13f15a4a4a6a32a555d5242711600000000017160014e57e5fd4dc7b84f881af361795868b412185181afeffffff021a482c6d7200000017a91438a6d0be596ddbfd8fb0a1cf35ead9ebc2ea1bae87f332a8000000000017a914c6953657b5c6fab2828c77edc7e5e700fef836a88702483045022100bd69389c2f1c94cce2e2875ad6c19b33afba780e1ae93dc93135bb48a94e590102206d1428f1bfd759bdf2e9bf46b9b896dcec2c4c561fac1f1b59e0a595309a135c0121028911d98adb6dba3393122eba19f1e567939a1fc84a2b1ca09704b10f6f413d970995150002000000000101469353dacd89b6ff59ab2e13ba629ec872fced5a461e8b5a1042d95bf8dc92e00100000017160014686125ac351b95dbcb7ebefc25fca05d84249f7ffeffffff020aadc3000000000017a914c69536f658c193d9067987fb8236bb0a8ad277f28779a016357200000017a9147e1448db2c9522efa8f5875c0be39d88f81a54c48702473044022035dc83a61873d565eac642a5779f2feb5493b807465545d7ccf7927226e3b4d802204066b3acbf00c36d7829e5ce2b9a58261ece4f9548605e83f44a4770707cc0ef012103e6ccc9d192ff13e9741f16432bc6bc61d25788de1003ce655a1cd529b18b003609951500020000000001012eadcb5064b1e2917cbfcdcb5d1b7277868915dd74f3bd5239ae4771cfbd22310000000017160014185431104090fbaf8a6b3f05e12e86b9638f1d40feffffff025087f7b16a00000017a9148ac81050aa6a96a7ac09aa6d1416da7b5190022687ec90a7000000000017a914c695349598346d08df5064a956b990d8bc42db408702483045022100cb36945d5b2b7140505c0025483b37c82dbdf0639b4780aeea3c3487d67e68a4022074b29bdd4297583829489086f28c5183aec187b77715deda4441268a8be3593d012102ea80ca915680190fd51356e46fe453d5eeee1efbcda2c9c92b726d87c44f5fd70a95150002000000000101fc43aadb8035fa225346403acf919ceed6e88ce83f870316fe8b35d593c6c51900000000171600147d0bdba9ced00b34a59eb8638a6381c992742e47feffffff02ea97ee726d00000017a9144f07a43a65481039b3378ac21a9d6a712f9a18c987589719010000000017a914cb84abf201c83e35e4f10a282c8afd2ba45417798702483045022100a8ec9b9ee80eca926cd2b0c571d31849e6371474a9ff524f501f02e1e5e6d4b402206c101dcbf50e5b79ca09ce38c6c2f8fdeafb4267edb6347789b20ca06b44cc53012103b17841fd381360986cff657f64b55f50071f584272d973cf31d8ae950b438b160895150002000000000101db597814e482178a79cb768e3e1eb211e27dd6c7639b531598a012ce0ec6740a0000000017160014c6fbcfe90726b7f897a7ace5298da9b1ed1be501feffffff02934721010000000017a914c69534b86011829615bb114f3e9a756ad2f4480f870c0fcd716d00000017a91416bb1c1b3e49b9337a6f72509b89e7ffdf6632ec8702483045022100bccce156f1438282480c69b586406805969a6cc85d151e3970606263f18f87eb02202b7f3fb7958661d3bfbaab2d94fa91255908bd2d360711b948fb3f4da303170e01210335f28c79ab7883f522b947693d8976a1744b2079c8a890d0b8ffc07dcc4a60df0a95150002000000000101db8a6442842459fe5a8bddb052bbf936694b148a9568a04519a822af2297ce14010000001716001423e309687760617f65fb30f7a786364a6c144e0cfeffffff02a1780ecd7100000017a914bd85bf25c75da6006efcca47246cb2cb7a6d774a871a93d6000000000017a914c6953680ae890cd3b96f24fe278b3bef00559e3287024730440220757aa2afe5d512b686e7e23468962d56adc6179539e0bd9a18d7762d1dbbe6fe022071d8cb700521b64476523ef6ba316f95fc6094bc75c62b09eeaf527c7852ba200121023be81f4e4a0dcf373de48181189b04312141c4d7ba41e532618022e177dfb3ecac941500020000000001014d01f4ff33401e5b911aecb9181ee882f7e1770cd9a368ca67391ea5fc417bfa0000000017160014aac559e8a3370e8b31a14d1cfaccc0b8f70a74cdfeffffff02384d12010000000017a9147290d907b52c830eb516ef1b60789dd5dc113a0287704359a95400000017a9140b51ac599cb45b62c05c1db678a5a67d767548368702483045022100d85d79ac578d12ba2e7e4d517536c4b028fb464960bea6095205a3c75e874f16022025c5f4958330898adc042eaa878f51a1e2ea3da777e16846ffe069b2483f4aad0121039abcadf384b2ae6c3f3a5fed220375c6b52ca689ae655464cbd36ca6c81fce840a95150002000000000101e951166dbe5794896f48194ef742a4edcd54202daa2a667be14cdc79ed4e57000000000017160014a7ca7a41f6d83d9ed37745357c3739eb91e49801feffffff02930caa000000000017a914c695370bfed85540fd9d06315bdf69da1c609914878d620db77200000017a914bbdcfc4a919ba4eb23e5dac63e74de056783540e870247304402200dbbd1a4ed6aac4fd5c4b83b9e649176024ad22a634ce78302180fe17c1a203e02204361c5d55ff26f46d41aafd41c3a8ed52455859c0c8bbbad18dc4834925cdb300121034738f5fa80dd2dad1e2c33ed6bbf0a97d0642fa14edb63f8ae32e12e615d3e5c0a951500020000000001011feeb5df1bb85dc75c33b171fdfeb6a4975bef318b51ba6fd841666d1e2a8b650000000017160014e49331287f5c9ebfbbc035180eea7cbf982fb8cbfeffffff027255ac067000000017a914acf2419579eefdd66583cf3fcdd45a98596ba3ab872765f200000000001976a914a349cb4ef33192250d58ee28f2452e80582b8be388ac02483045022100cbd9659531a006575c37bbeadf1e3f2faf51f4f5bd409fe8b3e9c8d6acd5606902201b77a7675b7e4a7aaa4ea476af6dd2634be88eb2ee3417f1fa18dabe2de5f1580121024c8059f04509f5e3b59ce33c124fc0e4c66b9b2ffaf9930d90b4eadcc7b313e00895150002000000000101ef96515304c8e026213ba74a52462e13d0ba971a28d9f6a1563129d8ccf3351500000000171600146ee0f9a8df488633f2d97c7e2061a15b6fcd0400feffffff02df93fa000000000017a914bac6e8486776d1225ebf1dcfa86c622ad91b1079874880b1057000000017a914e4a520b445ea4705014ed748a70b4bb1540e617d87024730440220103f94f8fba07aa5b4cf8d4ac1ced5eca8985d2452eaec90158194fd430cad5202201a5c19154ac79f687a1766f67ea97fc62c284e6ef0013483fe2576b92fd4bf130121028f107abe4ac89ffe140e6fe0969fd580f16677e63ba2a067a25545bc3950092cb294150002000000000101a9d9735114ba81f608b898806a6e060365f3144ba51661835c3edc9f9a6b4df90000000017160014f2c200e7c7f08fbb1de3631d123f4f598b9553b2feffffff02c8852c01000000001976a9146da155da63db9d1079c88c9ee16d7324dc0c5bc488ac4d95bda27100000017a914f19c1a31e11511b018bd62a8d731db9c2dc2e6e78702483045022100c0c9306f8f9d5d01829270f3846451497a681dcc0b74465bea7b4c3af86957d102206d4b237cbd3ee177479c1b92f52220edd6605d410cf1367480a9d1f32ee8ed16012102ac77643950903e05f7105f354f28150e0969d8706e6d289171e0b3d42042b5060895150001000000025184d92b58e9afadbb4203e3f82bf35b02537a4ca248276cc458432a53cbf95f000000006b483045022100c6d3b304aedcc5628cc00102aae53636fa288618cd4058b77e33fdb4194bf32d022075a2c17ed1402ac7ba8fcde860c4283261a93dd06c3c2e0e1d80d34d7de351ac012103fc9af61a0566ad8d5bceec812b084946b6a26e9acaa4c5bf827fa4b9aa60bdb7ffffffffb8448c90934a3aa0fdfbf82fb3f088a758bfc98366381172caf699b62896748e000000006b4830450221009d6465a399c10c4baba9f370949523afc7c5d9aed99d89848ccf89821686ea970220468195ccdbd2795af639ea2ddf4c705e81a66d14fbf10afcca09d5f3c85620f3012103c13a0084dd2f1051487ea77cd57681678e60f65f29efd461fb0ac9c37488925affffffff03f4e20000000000001976a91407b820a249ebd32facd81e83295dcb96f67bd0e088ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363a0860100000000001976a91407b820a249ebd32facd81e83295dcb96f67bd0e088ac000000000100000002344dbab0c1d20b38aefb93e7055b37d83052a7329c32e9fe3bbda6addb31f852020000006b483045022100c3c3263b2ee6da60c20ae771dc0baa93db9267278b972a2db8d58a3deb4c58cf0220752246985c0254cd55e87cde4261b5999a69addc984ed554c31b5dced48b8b4a012102df3a390708815ac318830a38bc0b0d05f65eb6f6e7bbb9b569904e7268ad1522ffffffffa98596ea46e08a355e2feb74887c15bf18eab2f2b910d95d4f2e3ee26f4f8f80010000006b483045022100b6419ef6cd79535b7681791eb6e5cff87ea464770de4284b4a9419e56d39ac93022030a5e9676ae96a8cb664a3d8e07c4c0f76a7d5a63286a631162848f5af5e9686012103f7c01e103b41fd7849691d56634f3630e6ccf7b754b191f75e5c8206cd21f1a5ffffffff03a0860100000000001976a91423076f7400f78503f71581bb1f6842abdb0d1fac88ac0000000000000000226a203966636336373631653438633436613362303566303463373262636438306363f4e20000000000001976a91423076f7400f78503f71581bb1f6842abdb0d1fac88ac000000000100000001c61a9cb49b86aa32cc8cd0d738e822608e6a6c800018307994f10cf5f86c940d000000006b483045022100fb83d9109b564538fb37c17d8f74804c97c7022eeb55e2d1781ef9670e63255b0220532e1a9e5957e7de80ab28e1a764985b4f570fc28cb871a29003f149f127ad210121035780c7e6fbef30ed7d6d742e1e40f7a18c69208839c57c4815c20cf219aaa23affffffff030000000000000000226a2039666363363736316534386334366133623035663034633732626364383063632b76e103000000001976a914d7f54efa4816b5c536c5784eb99ca899663ea09388aca0860100000000001976a914d7f54efa4816b5c536c5784eb99ca899663ea09388ac0000000001000000019c3105f363d7df67f626c9372f780b34dd7a8cdf1bbb38eec95d814e93043d6f000000006b483045022100b1c5c3c7561625824599b30ca1b613db31f80dc57661a6038d82fa1c60916a8f022024104c9b73a6a4fca55406397fe92399d6cf4638103c0d5973c3af070f1f83e80121029f253b282b26ac8a7b18f2efec3a6fb85844a9b75b63a0df122d5f599a6244faffffffff0356d9d200000000001976a9149ded7cebf156c7e0da58dbf396c6132956b389e688aca0860100000000001976a9149ded7cebf156c7e0da58dbf396c6132956b389e688ac0000000000000000226a2039666363363736316534386334366133623035663034633732626364383063630000000002000000000101514552cd305460867f5cc32380c3155b0081236a809b9f6820fc84753633320701000000007658338001e61e0f0000000000160014998e8b4f7183f3cc3914bd15d2a2c1ef2ea56e9b0400483045022100884c019cbc21713d6ec75614ec469b97380f920567900cce47dd7b6ecf0941c402203b8237bd82e14cf308e78253f6a4e0354314750b3cbebb0360d249d8a7c5f11d014830450221008a4ebc34e6f7d4d9ce2a1d1e767ebf0f94839bca2b47d16a7107f8e980e6864d0220057f260cc20f732860fb534ad7fa127d46e288371cbfb1d9fcf6853451432a9001475221025512cf5181dc5af2ef15e3d2709767c5b9305ee67090ac6cd069fdd9d9d06d452103e0f89da189a2863911bb00d7255ec2898c711555c6936743cc96f9da32e7710852ae8f4289200100000000010154540215a6b4407a7b19252dcc74375ecb93c26f7ffe8f4633634298c084232001000000232200200f1ba56c6848ff3692c2107413958f5a67ed772bd9bc1dd3e9f0ca934cf45bd2ffffffff02e0659800000000001976a914efb15b5bbb259ba6c81de1ce91d7f372f9a454ee88ac748f94000000000017a914f1b43c61d67fc5ddd3ea3784e203a19f703d85fc8705004830450221009dd3666251f7e7ef8808181607242d795e3d85668a6cd6c76aef06e4d23cab35022029b37f4951f335f41d8230cd6b2ef825715d4eac648e7f1ed090404065b22f6e01483045022100bfd80950435f9b17fbd704de7185b7930943c2222efe16d922ffcd2896d38f1e022062fe9cec23aa488f1169328a50d26888b527de64bda49541851c7a7c5d36bff301483045022100d0715b70a66ce14e61f0c3028bf6854ba7d1aa87d5ba2f2ac060633ef96a7f6d02204ee71ee09355f4e6efa35ecac8c5ce43cc9b812c06f6d191118e4ba77c14f3dc01ad532102811e55f330fe7c4ed6b4e68fbc341404358eee5dd17ebf40a116c2a6c6a50eb621029a7977a947fa52a1a15e88dc135929683556c4cb607341e051d49adf9d03a8912103104b5597ff678fa4b036051db1e705d5120de7efcb2cf3cd3cf85f6c63d6d3ea21031237991e88f6020d5efe3c5725f54b27258973e258583ccba8e1d4e77a74a13d21032e65849774e52d7897f9a2ca06e40fa700922270053f9366a7437494c6944e0855ae0000000002000000000101f87d28a9381ca598e31aa09bc5cf340a26a52521afd64bf75a74417114f3549c0000000017160014e9cbf3507b1bb1dc81bccb0fb5a2c207c26a178ffdffffff02002d31010000000017a914e57dd34e18c1dec4b05805f8bc122776c70bde00872eedb30a0000000017a91403d022ee3ef6f70876da083dd1e8327ec6e121338702483045022100dea1e480693c3759d6162ac6b14925759ab079941da5a25a98dd6c8cdf25eefe02201b33e7feac6f7c48c550ad489ba07cf12bb3de28a3c8f056191ac6603045ae4401210373a7ff0a8bf74699c6751e9c2345c3f37f93b0e7c7c96763c9330998c9ca4a920a95150002000000012ad51c58d005aa23b110f24e3f1acfdf9c67a94ab093024aa5bb367509aac8bc00000000dd483045022100a1463c7e8ddcffe11aeb3c0caaad2e8ead6f5a5efc3cae38dd47801ab78360da022041b2695a449a622cb89e1d77c7ba8450a1daf74d3aa9bc7a8cb44765a9edd0aa011fa07726a1973c76ff1a33a6837795bca8265ec0721f09988d75db6a65688f04004c716303149515b1752103099e3d916da2a2e546aa4ba9e4b82fe531d9a221e899253ffd94bed9ffa9449e67aa20afe91cfc34b66fb7b46833a305197ab245208e52b8e5aae3f48025d4008d76cf882103de92f149a67d6f6b2f451b45d416ee26456930d89a235f3659e0ccd4d30accd768acffffffff01f0053101000000001976a914958b7e34b63712b7b1319a42d7fefb301f61fad688ac0000000001000000057f91138e2c69a24f143e534a021ea17c0b9dfe4c594aaa3a0d7ffdefb9503c9d000000006a4730440220384e170df452c8a0e9e6bc7d61831cd2882279b9ccd1e26ea41d601e60b3a7c70220518ef41a46116bb8c383105b42b0316c795aa625b3e443a45f4c3ae8d539557f01210333ed2e8497d259dc3e169a9c25bfa34431512c3d747ea46e25bbc0bf47fd36f5ffffffffe288640f0c86bf32133d0450e61e14b207c72a9264789797188369512c93ef76000000006a47304402201c4e756b6bbc03b7321e81c1f4f6c3013757e48b208c9edaa758c3599860be5602203584c4e993603344496f8d3d065daee93ce023d84490d05a2f6efffc869f272e01210245edc5b35198ec69a93ff1d3d53082ed4c9df04373f42c70fbc8d1300e53af21ffffffffe288640f0c86bf32133d0450e61e14b207c72a9264789797188369512c93ef76010000006a473044022014c9418c66c91960dc4564c1dc29b50c7507627e70bdc26c176f6869b3ec80ef022019de9af87d4d2e25dc8926fa3ea7274312a15451f77b2233ba69c44f753d011b01210261733d75bb243f2d953073a4ef45f8cf7aa34aa25a0ffec69d18d9cb66e4d30affffffffe288640f0c86bf32133d0450e61e14b207c72a9264789797188369512c93ef76020000006b483045022100dcc51e73358064a86ba58a5a6e293bd0c470138225098713730c26dcfddd846c0220799dda390222162385f80f10cb4dcd682567bd4d65889cae479fdebd79cd99080121037ef863cc2e2553249da97835c1426a563abd169965633d81939b52fe27c8fd20ffffffffe288640f0c86bf32133d0450e61e14b207c72a9264789797188369512c93ef76040000006b483045022100ecc8b1f309ccec04e87a35230a9e8f825add34a7178c46bcce0553577e53938f02201ef2e68a4b9a9408a4337106f8b3a4d466e6bdc047fe4fdc43d8d24ae84b6b1f012103224bf0d26b26c1df6ad049fcf825d391165e9e33f9408f74bc13ba07d963397effffffff029f0d360f000000001976a9149a931593398f468dfc91de26b1227955c9e75e1288ac67673400000000001976a914683dc8533ab1d85023f180134c606e8cfedcc4bd88ac0000000001000000000103be6b22ffc32e0c9ff2ec53f2f3fd40cc0be951e420115908cd28a62f334668d601000000232200200b84438d1062712233907d9aff20fd07dd4b84eb5b4f44ff876ec31747244a7effffffff43bb04c5ecce10b305af470561fcf6feb051ae55b4db69b1a6d2216dc40199720000000023220020edbef2842a027b5d04696ec997c553675add90c365d82c1ef48f3aa698ab5ba5ffffffff848328949f3861615a302a1934792920df4c3a25b03a7e3dd3df54c0763d87420100000023220020cff430b23c6c31d41120818d427441bad70acaf4e4ffbb7071e26aac3eea19dcffffffff01102700000000000017a914c0dc814ed3b0843fe3728ac177f031219ff423b7870400483045022100bd943721154704f3c5edbc2c4a8cd5ee6753a73ba0036a69f91ef3cf029cda22022039be5e55cbac4eb197758192bf530f72e22cba7856995350e6de2302652225160147304402204b1152711bf2ea04ecbdb521400441f9f4a54b4e9c3d604cf8fe97d6552213ea022051bc56713f24640956f9917b4c858dde07f7aed9417153306857bf035d22f120016952210216cf555d860e6811c832329773a1263e89e50750c59c6a454241daaaa8194b832103f0cffc5b978a7b1a1d2c93ec96c67dc0a17bb4f87e4dd483041a91ad309d289421024260868e351a280f9bdd17261c6fc72953d8e0625760643a0315c2ac0f732d7b53ae0400483045022100b5209abecc39ca24cad846746a6b1f6c370ac7e69b60cc132a0e53d075eb41be02203295ceaa3faa0fc561a8caaff37c6c7c94afd826d3053374c7fa1b67c5f655bf01483045022100f2edf02c65bbd862e7f9fcb6b0dbff2349c7a7ee1f995c485f2cd9acdd43a51302205d3cf5be1fbca2b05c2fe7caef3a45196e2a4d5809846459a375fe4665d365690169522103bcb5f3856514bb59dfafcbcc8ec65a0722af0d78aa1ded2c512aafe21750aa0321026f8ce9b2aa7d360c78eb7483e39680e93808f8d1e0a7e35e8bb54d637e8423aa2103fe3324e621c080362e63920d7e8fb06fa9381d7d3f6804cd9f368a53fe6997e453ae0400483045022100ae6764198d60a9cd896b042cccba9da3a71a805f8d738b7161af45563976e3c302206e7344d386adced05551c6274c1d7e346ed6d6abc810b9cafff6b98594a9672601483045022100d0e30f3d8e753d9ee3896dbc720f5ed79be83fcb22502bd1e59d8c59a4674c5f0220235f693ec6f63d9810e6e0793a726abce489e61c7fbfef8340a1824f342c2cee01695221033c6ad9d77c40b026f02e1db9ea96c82b934bffc1bbaaadd04f7a87a7b22939fe21025fb078dd2411bede5b98e5e39e5d7a43d783ee640f9dc2f2de4e64dcd483db7b2102e4ad9ab1e4d5a2aa632de1e548a21b58b0653b5c874a3a24a93ef756e607633a53ae0a9515000100000001ede1507c755fae1b0d2aa0785072d4da64a4031708c5ecfb873f11469fd6eaee010000006b483045022100d60ce6bae8ba4652be843e2d5d5da5699997fa86e28178ccc49eaf935aa64bf502203337390a3221cef2456511379abd2a07efa7f4936a411ce2dd8fe4b0341c3406012102c2a93a9fba8af1f603564e66731e31c1b2fc178c4980d563ac15b131dfad0889ffffffff0210270000000000001976a9148cbb5e1800cffa93701d747c0d6fe927ec8adfc088acbb60ce03000000001976a9148ea97e533889be4932ad306ae15002348e83351f88ac00000000010000000001011ff178205154ba1dbc0ba9a0199405b949b1a3bcf20164188b5bb31d511f301161000000232200201644ce50d5e50f4a9ec73d13e2e1a284c05486ad46458329556427ff14b51c2bffffffff02f69ecf000000000017a91451f5326126a7fa37aff10bc914f583721423e6d48740e201000000000017a914d00cea51a265f608b1fcd1280bb4df62a6cd3f7887040047304402207e719dfcc0a37417e6dcfc60c1e93eb4f734bea31628c914a8f6ec64a337d78e022011a1fceed53648355b6426fe1bad04d5d7842ef33d5d8d3d3b49c3d18547c3cb014830450221008cddf6a012af8e7643c5f9fdeaf110fa30b6500e5a13895a4b67e51e8f20366a02206108c7aeecf46da4b9e7716316528f9d6b8885c591b42d565331ead23813c680016952210355bcc3d8557e091b621c23979dbcf0077721c077eddd6c9c70bb82bf05e8e7b521028a304943a113f96eb7b48ab5eaab3fca45f2f93858b9eeb68564fae5b8b3aa422103de3cb24ace21ea5bcd188f8bd6572da0d4aa3b054e250e5cb3e16015c249bb5c53ae0b95150001000000000101c160f991f902bcea429921769a1ea49951e3cfa4487b9c7fcbad41b7a2a9d86900000000232200209568f5c582a0bf5e7b60d2d5db7b121d002ce0bda1cd1798cd88552d254308a5ffffffff02c29600000000000017a914829f2c512636f013769ba8fa535616cb85582524875852d7020000000017a9143770f3015646506c30c6228f66c02a22c31dd7688704004730440220218699b7585fbf444ced96659499bc4a8c464f6fdced68d3eb5177eb9fb97371022079f794bc6ef97db2f032700a3ea54715c0406e346c2d26916a7f23ff51eab9710147304402204c6fcd38c802392eb4392f7106c92120d77c9b8044d4972c4ef06c1b456e6dbc0220520f26d42c961d4b575172685bcf1f08cee2517cf8e2236b7a148049bd340f27016952210341b797a031f59500d313a278adab80b56de9b908f8f36e2b40eab89b654fa0942102542f0f20b27122e18319c581e24e969ea9889e1681c76a331bfee00cd0c7046f2103eb2a84856cd8554fabab3421cc3118611b892e24c026df7ec1d6d2a1b57860f553ae0a95150001000000000103c4f66fe2ddb9b27ba735c52744d4ea6a2f51214b4a5c3e52d0892ecc6b3a8cc3000000002322002092210c58b153a2eabb934dbb11f3c3e58440b2f8c25b5929f9e6f25c27e35b37ffffffff74a1f95899578979fc7ca53c130821b82931ee1209f3a3a97ada5ea2e239c64d0000000023220020bf17d79fd99271568179a968926cf5a87723540886d4696c48b4c40053b60c53ffffffffda1eec1c48a129607c3cd0bb18b255a6bdea9646c8d6dd748d18a722961e9815000000002322002089577181fd673f45f7238a9a8ee3ebe87496d647cb8b2fc33137548138a17e45ffffffff01102700000000000017a914c0dc814ed3b0843fe3728ac177f031219ff423b787040047304402200581811e26de2b5a32a5f35835eba4b7e8ba7c3ce217601360331c5659f3547a022033a08ef3cb3541228ca01eee8608ce6029bc702aad920d492c3325c5e45ae00801483045022100bc16d3e8c3e755cec7214fa9c2fad0a70683ce0c41f33dc43dc13ef15ee335ca0220583186549c1bba750f70ef28c268f249cc8759126afd1c658a1b89dafc51264b0169522102e7f737c2c0df9ce8f6019575bcf79cfd1273e217bfb010def1aec0b43983891b21020429c1e6b947466d3218287916e23df08a98962ccce835f36602e9144bc7a95321038bb7f67712a7630248d1035ab10a2e6eab694b43a6b860ddf6b572cef12feda253ae04004730440220036f466aa128e4ae8c439a34091bb4aed6682bd0142b15e6a354992fe7da126d02207fafe53fdcee0e9a3aaf7584ce97c588ff508e4da0a8079488803936da56b6a7014730440220036ba3f018b0206d76a306d1406ec2c1119b93a16f77867593d48d9279067f7b02202f323472b94c7c56686c8a17463ed9fb47d9e2cfb4c52be46781df402a5ba63801695221027e8d859a97644709c5c21d88d9e2c173af5b3d01a33aa6903af2c65b8b081a0a21038ea1622442a161ef7c906f062b7bfe9da260cbf4caad20d5adf7f92285d4528421033e8700181db3ac92f521184820ff8c4e29dbe58a98b94622ae2167ebdbff0eb653ae0400483045022100815ecb08184227711938619ec145a064e1c1b4764dbe019f417940ec4bdb899202207bc68b7f47c91be6f34f1d771071b47ef226e361e9b89e8e6930f85eb9ef982701483045022100a4c12f5de46719a102c8ae2034191758e1fd048f6230b90f374d79a27aba9004022026237658d247d3ad3a09ac4ae64c9cd555a55f06f3971380c6afef92702f0c8a01695221038c1182c40ca41e140c3c72b64b10c53cbf802aac3866ea4c400e6d1b2d51292e21026af20c67a030766b9ae4744eff56ad4bf75cb06c89bac8ce9ea60ddf690178a321024968d4648833b1f861a01a55b1c64d90bbb5d45580d696df724210cfc73f4e7253ae0a9515000100000001086ba0acfae937026ad4c44db6cb07cc0da36556d73c2a62ff853add7f5bfb5c00000000fdfe0000483045022100efc0915a59d31348e65235964887fa0cf4775b1d7405c0579940ab84f44ee37302204e634a42cc7d757b73e5e2b50f56147cbcf0066696af7c3768d7fe6629d7657f01483045022100e19ad471b72284390d9df8ae6c00197410447ed09fac077574ccca240dc53768022070c38fe31c437a236c0a571299bb18c895ddf26cd8b81c4e1880c26a4a7a3c8f014c69522102c7cab22b544b0764a175d88c540df2f96b02f67c2a6a9aaacedef95709b897b221029220f3c9b912524ff0e3ee1daa31aa4c971cfaa98a2b4605cc89118f0171753721035d8ca2c5210221b94f8e3eeeb051bc3bca3e71eaf74e31d7c295fdab1923403153aeffffffff02821400000000000017a9148b25a37f228853be101c058d6e257af2d8907db787103baf050000000017a914f02877265a28f2198017af995d63e2197e6ea47f870b951500020000000001046e3f85711f3bf5e882ab230af0f1273ebf1091f8af16c5259b46ad0d7f5f411a01000000171600147cde34b01b1108e93d09ac7c638a7782e8b834b2fdffffff9be5c8ecee24bdb70686947834b9e0cb20afc9f3d2826b87f71530bcfa7defe700000000171600144bb1bac9d8faa05c75df5e995c7a471667f81ebffdffffffaf2d25612839e317da92429c7561b99fac3bcd2a0802097ceca21cabbf5d0b0e00000000171600148c0662e9760d1b88c11fb5876be6231011cebaedfdffffffcf534d9e8b89b5e12a2dbe876a54bebc6ab8843c74f0db85ed3f2d59f2c5c7c03901000017160014a2a3a23e36be44aa5b93e030b5036362ef539909fdffffff021c5f1a000000000017a9145c3fd5166cca92bd28a57e5dd6ea861c43d1befc87002d31010000000017a9141f5d50d9f8403313721046337663244203f5ef1b8702483045022100d55419c7d38220575e0d7ccdb7d476000235ffd5f1768df39aaf50d9a8e8fdc602201c640d963a27e88606d333626bb87287b5f7742b491f0e820a25287941cac0b40121037ac99a042fc88997d07f382c1f5dfb338ff781f299a2dc47e551e546285e92b7024830450221008236847b7eae6b9a866e654c60b8dc9d1a6e1bf2b33aabda4a35d3fea404d9fb02200ea0304283a282a7cf7476584ae01962aa2f0e33a0b05580166567e23569488001210244d93726a1f9664daaee7c4df5249d254b8f81df1c17a9d7d2a26d17813daeb702483045022100ac62d1b2d931b5efd0e7259e06a932fd3709651b1ba5134431b4947bea7b06c102205b49e7b9c78adb3a509e301f75b33ece453beaa6d36a53be97502be86c472307012103b8b2bd14e6264d6caa2f0d21fc70b5faa3da8a29f074e158f25eb2972b9b033602483045022100e30ad3372f29f9dd1825c9575fd76e9bf18e105c022b8463ea62f851fb1913e002204f9c9b2767302457ab127a27f143f595031d2ab34d302a03d227066b8c4878aa012102731062ce358127a1daea088f1774928efe81018eb165c8e6fd27c91cd4c9a2b20a9515000200000000010110bbc503b5b1743bf357b6efc8f73fa8c311593a129c2f45f75a288b04e50706000000000040d1e380031127000000000000220020d059f2cb9b7b321908c0e71b37167c05e7a0e951c984a39502566f7e866d3fc756c6000000000000160014f8879197f6a966aba3bfcdd53c2ff664bf0a30a65d0f02000000000022002097fbba74f03ef017307291531b0788b29dc46846c13e6ba31b0ecbc79745285204004730440220247c1b0ba94ead5ca17f87c0921e6f6ddf17c3c01f8630181511bcc5a01c8c0902206784c70da7dae5e1e636f3381c676a3cdbfcc92dc1ef7170a83131b19f3b9e0301473044022065f5569c8edf2896d37d641b6019aeb9991a459a4be530af2411150c59f6467b02202327673725bd00290bf85ca3a972bc2acb5610353521a2a8b3d55dac632d3249014752210242f48c5117996e3ae1575191268c78a20b7f86c6eef7b82bdc1d9c7b882a8b672102e8372981595ada226a3b4db2e4f31810f7cf08b5adfa0d15801217bbba6ecf3f52ae89c497200200000000010186ab4d1b3655bd6583df2ccc8f8772c3529c1311f20ccf2ce4bc43546ea68dd50100000017160014d7fa138e5d4a40a19a3a8d6d22bd4bc52fbf18a5ffffffff0200000000000000004a6a484c4547414c434841434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343435bc300000000000017a9145006aa3c038834326e6886d7bbe2b64e8ba093c3870247304402202586b96cfd707018048d1fe209b3ea9497faf7ff2db09e980e2dea1baa7e76a4022067e1c45c9e1fabc21ad5b96ea8a8e676758de846be3040a104fa5eb69572c43c012102de72a87bad2bd632a14be46933711772afc79eec87052a0432ea23dfb1be52d400000000020000000001012c13b71dbd73ff2981687f206991635a702e09057a9f27aa30a0430384d197d1010000001716001477d139ed888d8739a43b119923a0490abdb38726ffffffff0200000000000000004a6a484c4547414c4348414343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434321b600000000000017a9145952a4521afb02380a385165cd3736b283ce08688702483045022100e6ff66a53ee1557c5ed7fad06aff5fd7d2992578b2c1bf0d1b6805af8c5b845a02201310ad5d3bbd40b81261f72e38739f1faac6b9195385f621c7a22dece35b26940121033f3ab5c96df503db9a3cc8d0ae808ab32b6b4fb3bfe99d644d98fa08a176c6db0000000002000000000101bc04638b0ef1827067995250ccd78e1ac2cf72cd42006839cb066191212e46880100000017160014afe4a27b6659c28e9a696f7025ad579f033325a2ffffffff0200000000000000004a6a484c4547414c43484143434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343e7a800000000000017a914a4dedec994538628ab95eebf7f722f406cd108d38702473044022077afe62b98a190a1698ed6cb0280a1bd4770e868a6db47f2217af7547edcd40a02201330f031beed1f5b9a9e72f444dfd6c603daef7eb18030afd2e468a30dda58e20121023092f1fe2dfdaac6b46c1709074947f514927200e02634bc0bfb04ff47f5dfe00000000002000000000101a9649462fa06bb20a3bfe93ff976bd2d76efefeba7d2a112804c1ee2ff21f9c60100000017160014ada31d1966355a94fb1725ec6cf5668e7ccf6faaffffffff0200000000000000004a6a484c4547414c43484143434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343ad9b00000000000017a914cfa824d0834590b3d3ce54fc30416b86e23dfa47870248304502210097e9579f8567da137519ba306f7c3725e4e82bd83fb294519e4df064d3aeceef022029c8d34b428fd9de0e80ef5f4f2c65dfd727bbd2414eaa084b98dfbd1cc713f4012102144b5d655ab1d207363774a7f48a1fef7e15bb34c3e9c9bc7c62f75e2b0c65e100000000020000000001013de812bdca13ad2fe3927480e1b9250c16b5be135c0e512ae74e0461eb86ac5c01000000171600146ea0baa45f0cb09da64cbf14237681c69a288d15ffffffff0200000000000000004a6a484c4547414c43484143434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343738e00000000000017a914170428a4310f5251143db14caf5e9e4730f446bb87024730440220343e34eb7399c3a6a749dc54e586007deccf675a3a5e026734697ea76496beb402200c483b378e48b5ced5b11aab65fb264478500ea8cc4d5b0feff168be44ca4b30012102bd82dc8f8f210dd01d65efee7833d403a9c896fc85aa3f8e8ca955ac646c659700000000020000000001019595e8360e09b7d19514c7616cdf420c7be5e49673397713bef582fe48dcbe78010000001716001477ad5a294ee30c6295ae8f9cf2e380ed6c277564ffffffff0200000000000000004a6a484c4547414c43484143434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343398100000000000017a9142e904bbb9893683e9f2c2b4817770b1db06048a98702483045022100c96eb5526264a8222e56eb43e29bd11544bd47eabe3836d1119f5832baf97832022009110592a3b0ff00e15ea3c1ae93bfb0cf580f90b2b0730f0f6bfc5cb3e939d00121033a6f590caee835868cae0242068d9e9d1fbd83da9b9a747fe0f0e090f099c3cc000000000200000000010153ee5d2db2fc00501971d6008477a2c2a09b9d58a2615287c743756c857622410000000017160014c918eb29ec898f4f8fe4fc0f27775095718c1fe2feffffff02509ca9550600000017a914f1ae3046b17835097436529335bdd1058283a61e8718fe52000000000017a914059ad42ae61150030c16fed8fe2da7897adfa9788702483045022100fc67048fd037ac74b5b50ce74a33e9ea9048ac3f5049bb90b277d4d7f14ca933022053d10223f08a48923322cbce1f36012e0f229c3b9a4c3d5e1583961e161a5734012103b74d5b1adf902d085bc105e1916024a4ee27d53910167349c32e389ac732c32b0895150002000000000101f85ab0628ab8568cf564ada3720d0b85277f04019df6d84ea333b404f6b01ddc0000000017160014b5e339d0408f7dec9b64e181efd48e3028df10c3feffffff02259a56550600000017a91404c6cae476e59177385645e117903b53808206ee8730fa52000000000017a9147e2a4f068200cbf4fc811d660c33b72daffc38a8870247304402203c778e52a27effd2fbade7adfb514881dc6e2256d5e3a5338d8c9f301271bcf4022071dd4a6d15c598764af57574b9fc48b61150b372ebfc07809b6f6d1128ed803901210307d3b17358a459aefb0fd2fde3a394f7e0006b1a2142f6e824e573d5b5e781d508951500020000000001014d6e37dcb2515889493bfc10e0c13dce8c0c3d7bbe2d2f73abed246d94b6c0770000000017160014ff4aa705f31b3481d6eef8849430cb057b92762bfeffffff02f0da52000000000017a9144732e4a1c9d0f327ed2d3cbe6b797d61b2f619e0873ab703550600000017a9141c2664b17c1caf56101ea1a43457f8dd958c58d2870247304402205e3fc4b457fb5797b9a6fd4cd319b5ebebc6ec817c9c5e8579c4e78d568c8fee02201ccd0e36588f9d123d3f261e66239a382a35f1d8ad1ae6cb953d3677cbb81a02012103d8085d416221f4e124b2751097ae9dbb9a28f0f2751103156a869d1aaf02481f0995150002000000000101296eca4813f5975028065457ce16cef0bacf42de37dcb9e6ba8841e2655aa9b50100000017160014d195c04e804fc9e768df6f6c4d3e14e3765d932dfeffffff0280841e000000000017a914bac6e8486776d1225ebf1dcfa86c622ad91b1079870829e5540600000017a9142a866f5ab5c9fbee3d5de31e595c6a72d5178c6e870247304402204fe9f59829111573a36ecf738289c52b0b8d20ac1fd425c87158645516beb64d022018951dd837d79434160eb67c1338266aa63cfd8f1cf6aca200b04ed504cd7ccc012103a35400f2a0e43d9fbbebb866bc071ec1bbca6edd58c1e3fde09f589824750fc30a95150002000000000101fc939af927acaeff5518852eead5fa88448a33140bd718a1f4bb5ffc7f895f62010000001716001480347fcd692970ebbfdc75281b4f9fb2ac5dbad7feffffff0290ea52000000000017a914c69534b2ae1d6cb9be5fb3dce8322ebab4f2858d87c63492540600000017a914125b3c91303dfa5dc6c37d43ca09fe39c44734378702483045022100ab43f9fe0570fd88b01d2a35708a1c90eefea6a08d9c025a4fca908d07dad40b02205c5357b97e32e594c0f348d4751c0444676efe8f26c4a312431e0b3dc8f697c8012103fd662fc1c9f60a9802dfb0eb0215408764a0fc41bbe1c25943ce216c884aa4060a9515000200000000010108b6b8ac3940acf11d7666ab44cd3e35a9aa1dfab88186923b78eb3c4b0227280100000017160014076e5cd492354f7a291b20185bcd55ae091af095feffffff0280841e00000000001976a91429fe0dad053fb06a51222886759a0eeb165942c288acdeb719000000000017a91452031c61723cba3da3d85dadfb1fb471cd54e9fc8702473044022001baaebe0e819517775bf49081f9ee207466d3940c5283f001cdf368b91ca9060220724ba62ff7c135e39f5f7270efbab7f978eec171f4965dee5cb596e338d4c562012103bcf7dadad05de558c2a5a2861dbd70fd0cf40250ca6696d7aed7ca9e0d8b3fff09951500020000000001017cc132f7849397966bec021fc37145b82646c0d104ca5ab387f3b066b21c7d8a0100000017160014f86b0e89495c2aee739f69a0e5b626465b8cd3d1feffffff0260ea00000000000017a9140556bcc5b9a58be3c6f25d00099ed4243d7c323c87d48215000000000017a9144da607d275bbd2ade877d765f2bbb16cf1890b5387024730440220505cea41ca434c141e357abd72d283dd3bce4f22c25524229f0dd76d0825a77e022077439d6a243c216f9aa9330587ec4312cebbf822a30d51ffaf1f7ef2509cac500121033311dfdd6b3e9bf5495f5abb533abf18de167e204dbc67389960d6f2a97c94f3b19415000200000000010248483f2a644753f7fd8f192eedc1230c760342c0964f1bd4d033c131959fe61201000000171600144b19477adec75c25b33d35577097957541cde1fefeffffff6a24b0c56ac5fb0be4063a1605a443faf38e93e03cba9499f01ac3e8a9a6f07a0100000017160014a2f8ec192e285f5a100c48f18f5cfb389b8ccb26feffffff0280841e00000000001976a91467822a594d14590235fc62bacf0d23624ee3737b88acbea910000000000017a9149c6d48e5c1e40362a9b063920e70177914c74d8c8702483045022100a22d186d879a6df6fd8d2c6f094cdadd11de3a0fbe49c3179af35551e1101ba0022050acac09a5e4e8de291c7adeff76fe2ea9c21fb0253d1227fe6e1150d2b6993101210335e8765d8c591c41c1aecc268cca9ea709eca3e36e5db29ec4534408e2aee03a02483045022100a0d8aab9a67d858e0c1e3b4a2c0d88b7731a03bb910db0d16e548b06f49fceab0220201dc778709e69137b8f873535130759ba140fdfdac5dff13e88da41ed7b200b012102c08811edaddf8c45655ae100ae62f8a3383d040767e40931acda759c0a0b623f099515000200000000010182cbcc1655e50dfcc0fe1a90613d295f6333b0104c402e76ea14a6f1893531460000000017160014860bc7a1457538e8304bf0cedbb117a76d15643dfeffffff02b80b00000000000017a9147645181ec12699d2e87bafb27325ef2f37eb956087366a00000000000017a9142309bf91e7bf3d82bde5104014808725c9c1c92c870247304402205a6fe03959480eabeaf5d9a662802b376f48dff23beeb47413f90221f803171e0220415915334d5d59e1e71e4b9852114dbf337bbe80ba9992cceb399339546c6ae001210299722b50384202815781b91aaba81c361679993b6365a6b40f39abc3e7b2eda609951500020000000001017e146f8a06ef9a530c1003deb9c49bdfbc1e8941bbd5571f80f065b4bcbfe89201000000171600140d717c5bd1c151dcfb2d974dcecc356aeb4592c3feffffff0260ea00000000000017a914bac6e8486776d1225ebf1dcfa86c622ad91b10798763b70f000000000017a9144df4fe62aef4ab92ecabc2c2267967bfc87c238f870247304402205f45351794bffcb75fd529eb68ec6736881dd34bd17ac55c85825b00b9135d5102202abff017e6c413f853df02c05b46bd9490b2e4ada937af0245af4590661104fd012102543d09c7727a51d1db1e68d75cacbf2e543f4c3ee540d56e5418157c0b39dd4b099515000200000000010225b8da125b06d1cb53447139b4d5abcf3c0765c32d9d1fe52432173295e8432b0100000017160014ad7d94fed6bd253c03bcc8b61b7e146477680bbffeffffffed274563793883b69905e238cf4a41910e05139627d8afc79ac14ab0ec440d480000000017160014264ef7ca3857aa8ff1f5b4b373b241f67ee8792cfeffffff0220a107000000000017a914e1efb2841ed2f1c6dc52ab0087f7248d49df34a087f8ff11000000000017a91413f40cc874e2320acd332d19585ef22ab0a97c29870247304402204916810c13dc333bbe5d7fc41f90a65a876dbf7b9ed4ce896214717c2534728302202c6edbc63cf4b82192c6e4911721d0cdc6d9a395bc24f426f57500af6df9d2270121029f924915861bfa10a526b2f1ec017bf2b990ed86a3d4cb38ada2649a8bf2051b0247304402201f8d88fe892c5b4cbfb0998cd26cc56852b740789b90af8733756fed1245767a02202e6ae5d83318c7bbda357950387cd1f9d8ca0f354f9f1f7725e532f572fecdd70121031f93debd5fd19e5f7c7aba4af2947d5324487a025e122f7ce51c815d032fadfa09951500020000000001022a5510e621db0f8af918a457158cacd5b4ebd1ae31e2b7a6fcbcd6cd73e9bc0801000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff52e802c5f20ae69ee0d2484cbbf69486422f526dc275e5aed4e2209889a815cd0100000017160014374aae29343beab385338b61bec5b8b52c9f8b70feffffff02c6c50f000000000017a914b13e54e1262c15c00e1de768b539b9bf3dafb3c28720a107000000000017a914dde93c5488667996133960d2422d5c56910c55a2870247304402204f7766fc49097ae17b2c6e1fcab673caa16114529307ddedde3ae86463f157620220213b38bfa1ff42353caaf2e75c1081fb703c8ef22cd2476756fc7cfb07ab7981012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c0247304402207686c8f155aefad01da8a08386ab39a142e44c269750cc3a309b9b9d3c7cc87d02205fdab8a511a548fe1c56ec747f4079926edb61086f6914edbd33e07b68843af101210340759857b7c4ae0ff52eaed504f6e411004b0e2827768c5f4c5dfa26420aff420895150002000000000102030a541ed85a1b816c0068bc5fdbc9924fa0a6c04484aaf7547f22ed7dc612160100000017160014e3368690109adcc138e22f9c7d92e40824e6a6f9feffffffc05abb741153205b6b57c8664b36346ca87f35a894f5dc3d3818ca2f28afadc601000000171600140e9c8409483d155f727c91084723364e8542cb2ffeffffff022eb80f000000000017a914b9c94aeff759bf2923114374f125766e17ec4b308720a107000000000017a9141ffd3eefd5e2a8d87ec4a547fd00226705fb4fc5870247304402204097732df8c23cb7c49b64cc56bfb7873cd1456372d9437c3dba28b466145fd2022043a093df1b15018e9239b8dbd14a37ee1d735a76485c0e9759370631c2b45b2d01210299951fab4c6557aad52bdf2097951141de66698be5de32344f24cebc0eacec4c024730440220210cf0def4247b625fd49f76adf914c66d570ca96800aeed0d366a1c982b1a04022003fd89162008d18a58c54fe8f404fd6d57536c2bc49d288ee1b01b57a7f8ef61012102bb58c363841bb8b972c56153dd9658bc06d39355b699cc145d8a36ce7acc45e70a951500020000000001025a9a341689449a107144c9d4abf8ccd75728905f7e64388879950465930c917501000000171600143d565e1c99d9f444272bf92eb194de5fd4546f4dfeffffffc05abb741153205b6b57c8664b36346ca87f35a894f5dc3d3818ca2f28afadc6000000001716001481e186009e8acc1b72997478ef1b4bc3eb272cadfeffffff0220a107000000000017a9149a43a45e99c27077e2ad1438c5bf87bb0b0d8bf787e8b70f000000000017a9146412f77a272416bfa06a7776cafc4e199243c84a870247304402201f678da550758189e432340d61b8c1bfdd5a848107af9c95a1c338ed1d5d6fa2022075b352c05b27b73e5fc9b59980af739e3a21a089211e9832be1d257730d8a4800121021fb4cdbe1fc50860cbdb5501e549c262044e461f9ef8fd10e6f1a4a8f7e417e80247304402206ce050cf6c2c5da1b10665adcfb2d2a6e4e4939dab9c27963cc166982859dead022071de3c8c03de40eaee5bc8b343e5fb6f511eb71762912678e3a87dd888f63000012102acf1f9c389156f3bad20cbb30c8404fdc03e745753b6cf0c76587a5113a494ca0a9515000200000000010270e26c73651ccc9a6794e5883f6ab1b4f6aebcd81cc796a6d33796f31d1f0ed40000000017160014a1ffca81fe1474e0bc6bc52fd06f48fa08ce70a4feffffffe6996483b3102ebfc487131fc37d5da6fad43acab9b7f1d342a3e02cb59a93dc00000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff0220a107000000000017a91456da46068dfb1ca806d2ebc25b215e3beee4710787cac60f000000000017a91412a5acbfd594b259a443ba044da017c647c9540e87024730440220143c1c2c0ac367635817bebfd11dd443e37d68c355e1df24dadda8304469a40002205c28c9220584b87f34e590e925de2372839372885d30619f2f2408773175a1640121036c3bfe3559ec476aeb31cb0f633e28c1409248d349af0c3fc0300e5b66c2ab7f0247304402200462d256b05fe25fa3e258e94ee0fb3f1ece81f9bb66aa4cb6698a117d3e18950220360ba7a3c3c84671935b97a53e08db5fb2e77f84d16ad552b57a190ec9439677012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40cd1941500020000000001038c5292f9d21f3d54da143a9432933845be1342dc7113de6809d85cbc54c2e3d3000000001716001488bbf3dd2c8603e602b6da8e0ce5f7962c266cabfeffffffc1bfe2b82f363c683ab99dfbbbac1f2574aa33381822ee40c4765397bfaaf7aa00000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffffcecd71e6b7de5dd51e0992e1da9bfd1f16e9c8b4e19899fa0e4213941e318ee601000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff0264eb0f000000000017a91450cbf6a481ab3f0827c3f7738d8dc7aeb3a86d498720a107000000000017a91494b1c4ac70c6232b21fc170b1a3dba036d337d97870247304402207b1661b12e2c1aff632deba97754b0bf7dc533251effb100f841f70afeb2e2d902204ec852aab33f52813166eb248cf4a7a251bce7d13f941964c56b0a5a00e7d18d012103fac773ef619e6773886de34b31c1462ec9620314caf7daa29c76e93b18dbe78a0247304402201980e4d47c3b8de04db8a04845520d42563bfc2ef6379dfa43c4a1555e26cab702207d594b6d7fa1c3e7f6d0d05cb329e331780ab15cc43cc8f2287a44bb5b32ab38012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02483045022100d3d2ca3d51816c36f8b60e95a2002570fd7ed19bf87340fbc6316feb976a90a102204fc72514fa341c4a020eb6998febdd4174ea8bc2421d0e84e003fb1d9ae3ae27012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c0995150002000000000104392b1a422206a0260b185471c79db67740424d89f9f4f538a4158ce00a6b9abc01000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff8c5292f9d21f3d54da143a9432933845be1342dc7113de6809d85cbc54c2e3d301000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffffc6927c8286f17d833d3a7529209537af7d9ff256c7c0d93ec58fdd409f49e77301000000171600141c45c732696af9a6035df6dc05d9f3827db1d088feffffffd9280c9df6fdc72025fd9d8403778a13b7a5dcc1e2c1bda54edf39fabdd6cd1f01000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff02fae60f000000000017a914c7ba878dab60052c5d9c2f4e6f228a4acba83b118720a107000000000017a914ba7cd1109ea176dca38e8b7c53ba4705bada04a0870247304402203cd9ccf36c3db434e1d5e2af8ba9f022e1c8cd7ec87034a8ee0a5f302141f403022057e97082a252d6a9bc856536f55af44b56516f233c46c51ab36db0ae741c3c31012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02473044022068fc8a6f46ddfaf90c6df33263524ef992528215b0e92a29fcc52802c8bcfb4c02201b3807e61f1bf128ec9ea65ea341ffe4ce379d7d029cc76c3dcbf53087a6be77012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02483045022100d1e5baa17dd95701fe5419d5cd1e7dea8dc4fa413d559774639b84d1022f982a02203938800ffa6c09e20b3dc6700ddd02187ca4cf4388b01292b85c116c633d1078012103938938988e4eb760949cb7ed0256a325fac27409e54a6b62b2d0e77c891aedb702483045022100f206511a3032e5d2c43daa603a9f7b9dcc5317899905032d1120bad8b8c64cc702207ee570af6ed32bed70fcc0d6e2a9a8250de613a9973d8b5f6683ff74c7e0afb9012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c099515000200000000010426be0303b73edce06a987f0ace63259182cace5ccf1b6b875f6d14588e7381ee01000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff590450a5ebf317c8b34d56fa41a3ab971d528714a4be2db87666425922c580820000000017160014448cc3320ca4c573a099a31ce52c37221a9cdf00feffffff590450a5ebf317c8b34d56fa41a3ab971d528714a4be2db87666425922c5808201000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff73cc260e749b8c74a266b07c023847ea6d0d4325230aec61ba3152bfd79987d601000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff02b8e70f000000000017a91454540960dafb2fd2bc213d8ef63bae26b814142c8720a107000000000017a91403bb68ac01a41d1db59dcdca559f8df115b684968702483045022100d432b745f5202b2ba2be53bdde2fd397f7d657d51e5479246c4460da39200c10022023a7c7de43e06baccde31e7f8a943b0ef59420ab48d078a57b29ae7fb28bf44c012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02483045022100f7e09b36a541f59cf4b38be1aa1fefb3b57a343ca10dad0860c49abcef425bd102206a5f2e7ab20fc3319276ed8d85f08b447379586b6a0ced3ef4a29a6c6687e4ee012102bfd725f659a043d1f794b3546c2e6fac3871230637156ee919e04e15f0f6f70002473044022028484b76bdd6729f010eb8dc0092c68cab51fdf384db1e24f6b5675b935896fd022023b7c2d8196dca16381d9d3e8ae1cc7112358b93f1ab04dadf33ae035047b92a012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02473044022049acd50e2c4707c115101a99538b56c4771cf549936c7b5afd2f3e84c8d2958d022030dd844552b5953c2168a699356975abf527d2c0079a6f73294aa0da388f7736012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40cf79415000200000000010503807893b27adf9f87fbef4daae54c6fe6784f1ce5f30135d151e2e91924e6fd00000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff9198b44e387a40e9de652f11fb7a64d334b2932eda1ba182f3da13b9b7d1a4ac010000001716001418111884ff8566900d6b8eea2c5a157cd25f352ffeffffff921d258f29ede6581eb4758d1aa628fad8e954c7fd7cd76e6a11c53716c3382b00000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffffc6927c8286f17d833d3a7529209537af7d9ff256c7c0d93ec58fdd409f49e77300000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffffd3bfcb39bd1be84690918396ace8c10aba7c9faff9ec7c082876589a8087827600000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff02aee20f000000000017a9143138c6af19e0092527ae5056a62d8a89bb83bca78720a107000000000017a9141e10f2e2980b4c66d491b33e06a92fa9acf31e66870247304402207d7271750322a318286762b4b488bec60cd16677492c32202a1cd2fad89cc30c022076e300246d51ca5c870213354a7362ea1166cc304c21170341a215ad2ab78bed012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c0247304402202fbc2a76770ca1e6908933c0c2a0da132a2aaf2048ef7552f6fd39240e9976c7022044beec3c5340186e34ffdd9c3d1714802b05033ecfb4e50bfa675a52a4ed211d0121030b9461f4c709ade409840b6b1f32c118f10664e96be988e85f54de72880715830247304402201e2a1a714928f83b128edff0eea0d10380dad639dbc6eaa9a65b5cb1767e653d02202dbcd6303bcc1c3ee96df1733716ebd9181778c0426e64b83abec0052e37cb16012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c0247304402202965cefe7afb32913b2487a9625ffb752c7ef3a4a2f8e2a4cdc5aac07d60646f022031acbf22cbc61ba6d06c082a6a18d7326bbfab53c3ab8ab86993d9363fd6407c012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c0247304402206e2c11f6ce92dc2053d2bbee1ac2e7514ceef9ae84d1b236de91a19e5b37a4c402202bbb2528bb408065844fca7f1a045463b8968e940e6970b580f6be279ad68e99012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c099515000200000000010270dd51d52891286990d5598c68ece5b29336fe3468e23f6c770838e77263752c00000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffffcecd71e6b7de5dd51e0992e1da9bfd1f16e9c8b4e19899fa0e4213941e318ee6000000001716001462ff5369493f86c045891f7297e1fc9b9f3601a5feffffff0220a107000000000017a914d87143444ef3fd0a1781790d78961b5650387545877cc40f000000000017a9143e2519e286692723a2d4d457ac502f21ea4146b78702473044022041938005bed8a3bfbd18470fc2b2e3741ba87fb1995247f7e8787cdcadbbb7b20220106d04083f3b11c614aba97c07ffd4a7bcc21cbe9791d5c2bc59293be9acd409012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02483045022100e4e968c6ce4975ade213b97d244e1cfe9e4e2cc573d921cd848f85224b698a03022035a3d4ea386598bc60150d26c00a178751b30ea9c647921f32833bdab53691220121026e69d8e2cffe5d7f443a8fe705c5980223587b0c4139b2aec91a67df51677193b19415000200000000010273cc260e749b8c74a266b07c023847ea6d0d4325230aec61ba3152bfd79987d60000000017160014ab8ef2b1c261bb8c5e748a67ca4c5a63ccd0c16ffeffffffabe92e41e5edd140916ceb32f66accbecdf34a937ba3d71d37b7847dfbb75d550000000017160014ba51887160ca59e394515d9502c9dc38f7cf90bbfeffffff0220a107000000000017a914e926ce6bd8c08846bbdd5cb88d2d5ead6a6abd1087cea613000000000017a9142f3dbd34334d16e45f15613be7c9637ef63df8e98702483045022100dbcce23a8d6749745ae446892988308ceff3324eaa24f8ebca07e7906973ae11022067c54fbf87e82f4c9dc0cf075366d99f1626163897c8caaead117c91217f8c6601210225cf706227374e40930ca7d7d360ad549b96257eef91c80c7d20b4d2a7f65d73024730440220459cc922d2ea00588d58558d07e29e49434bc77a4ea33778f7707ff4fd89dcee02201cda806dc35ab50e2645fd70fcb7ce0b868632bfd0701c3d4be06582a465e4920121032d6549e97e082749257e17f2f031a753075dc4bab26318c3f746617bf29ed70409951500020000000001023c1d655b8fbd07ee707599cacf943c1bc90fef46ef313ffc0228a1a54ce221aa00000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff3c1d655b8fbd07ee707599cacf943c1bc90fef46ef313ffc0228a1a54ce221aa010000001716001499aae071348b5375ed40b23f8023dfcff500d348feffffff02c6c50f000000000017a91421177c4bdcbf91c2c1f4e6123da26adc4ae8be9b8720a107000000000017a914e28db5e43eadc34485bb0865c313c1f17a3a13d487024830450221009194bc9ff658de1655fb8842c70854250a8c9f7db0525794d6a1eb9786bd5b9002200e9d55fcf7b5ccfcd80b3cb6352182abed7d06cc1197dcfd931b8ade357dc39e012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c0247304402202bf508fc4285f12de6da03d27e3973fee63d1ecdbf54c620a7903466645e5ee8022020c00f42a400aaa3d06a8bed02fc50caa890a3d0a56b64cc597b215f75e2c0fa012103cbc5a1fc0625e93d549b03c47de2991ccabba5ac3f5cbabeb4fb48ea5711f9fa089515000200000000010230e635d3f4cc80f80404fd47c90e0c4dc34b3132663a128ac6ffe6abb2bad3ba0000000017160014ea91b3acc94051249eee19216f38d1daa9b6e618feffffffed274563793883b69905e238cf4a41910e05139627d8afc79ac14ab0ec440d4801000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff02cec70f000000000017a914e2ebe933ef30a1b79d140622691151e376116d4b8720a107000000000017a9147fc4b4646eb12cec43294de7966873d68a01ae1c8702473044022022ef1915f3ada5c6678a8e2f167b5625960e431fd67a547296f6249e7073bb620220624e269e916c31cce9f23307d384f0f9fcd04045fdc64da8aecf0a15288004650121021e1f27a1cd61319eb1017c91a022ee8b8ecfa4b51a5a03156dfd8b98fcb88a5902483045022100b7b7d53a173bd4301b556c2b3f47ce4be722c5291f4f76e3c73b440147ba4dde022022c9f34a3d31e75789acf43e65465d3cf881eba1cae62df209b20626f4623cc4012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c08951500020000000001032bb770e8813c44054ebdfa957e419488559f747f23c397f19bad710edfd8209500000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff358649d1777b311ef27ccf811ea73d6db700acf670b4721c48a26eaa7c7359520000000017160014483a384f14192991076f77890ddf0c84ba687b15fefffffff178adc2b23e17e4583fd7f3da4f33df77dcd63b1a769cdfc8b7f0bd00f32d7d01000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff02deee0f000000000017a9147efd49554d5c282766276f91e100480e2bd19b358720a107000000000017a914e6dc3cfff374ad2f50e31840334c65c7863a4ea587024830450221008e4da28bdf9fd4f2be122440f9506c7c18fc4572603cf157a2999f3801f728d102205a53d9fdcada481fe7af16cfefaea94bd0321092a816eb58673d49b18956092a012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02473044022028286b654c08df4a0c5a3c53e8ad622eca6293c54fc7a5522306e159bba77735022014dfe7fe3057c01e502ac9ed9813fcfb9716494fa6435da8f2bc805f2b390b090121029a92013966a4cbeed1e6e5d7f1801f7f56530e4871dc002910dac5016b73744802483045022100bad1934303cc523ea7bcc93629d319fb5478cdc826b2bc526549bcf9c7373cae022032a7b62ca3ffc3c4b4074d863053209a168421d9e844c46ffa7f3480f327a692012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40cd694150002000000000102e24775a53852c82db3b0fa4d8192d1588dfa45a42b83828694d3984601bfb011010000001716001429a13c9b454a2def53560b6fab8f028c52ae2714fefffffff6acd9433e76fc68fbf8ff7726ae4da7025d8fe83585c025a31287769e50360d0100000017160014a999379d7574c32192a60ce5e65589777826a359feffffff0220a107000000000017a914e6390db13c5c49f0db00054a109eba0f8c4200e987bcbb0f000000000017a914592cf12a6315b95c2f7043ca6c38da03c8b9969d8702483045022100dd77ad768bd9e26d5cf7db72770bf5a3a029cbb591087380834b1e18ba684b55022019b210fb8cdaa51885cef7da3917116f9ff8976dd093da6f0da1d739f574d40f0121026f3d05604e03c633e3ea58cf5db4b149456655b57f4da69a4131b2f8d79e1ca602473044022064cbf0cc5ebe49af20377ca36b5ca45b9e7ac30dca8dcbf1849f3d5e341fab960220778d93db475a74bb8712c66a1329ede9dcce614d0a3cc653dca7ac17791b0169012102902e8cd478f8d4e7fd335987aaf13c940395950d44ffef35c168113cf6b691170a9515000200000000010298432d18d626551dbef12e28801ed9694bda546b04035d76c735a552000e930d0100000017160014df493e4b17719f9bb1fb8aa5b1380dcb8032ab53feffffffeb9ae51b2a5fcf8438df5c12aa10c296f4d989c4b2f6d85a89960e9ae700db64010000001716001402a4391011da12b5b37d1ce1555700337f7349fcfeffffff0234da0f000000000017a914fddc06f8d27f7c1544f0ba0045c0a202a78d27af8720a107000000000017a914ebf9a0db5a05c6869fde201674db4d37f36e2150870248304502210093e2aabd9a1f925163dd206d5ff8cf2e0797d5506248a918a4b5a45dcd4b620e0220496eedc6a6fa3754db54c1352ee419edc14e9efc2ec3adddb941c6bfeb95afc801210260ff3ca70ce2f8ed7aa78c70fde015257d84bdfa0660634b0b0f681538a6268702483045022100966fb8a599559507cc46239abc1991d9cf0f0993487ef6bd063b564efc1bf89902203c1adcf99ea1ea792a9b6a23ead4381172907cfde42dc1e2fd25c9999c935d2201210212515623bf543d6b4eb0a913af153de84cd8e30a3863c3a0bfa8b7e72dfbdfb50a9515000200000000010267557daf77c7c43220f9eddbb15ddd4edb20a9539b07d561c9fa28cebc97e2a00100000017160014e3dc59887e54867c4528bf1df62c01dd6c55e614feffffffb1d258163eff1e8f6a30d63bf743233e90c4a6d9426817e39cd0e5cf2a5acff600000000171600149fd74b751d088c2735469979a0c13141573e733afeffffff0220a107000000000017a9142df1f8aead3e2b4cfbbdd3728561ee0e01d2ded587dad90f000000000017a91442f9d97de682e4c07812ba0f1fc57beb4b09bd8f870248304502210094aeb0f0c5f36f2ad39d0114a5e41b7dc51e3edb0a05ce5d3c91af5e038a82b902203e144937a22c11ade5b3c6ebcda3f28cefc6f01eefd93bbcdbd27f4d25d750100121030dbc744fa68f68c4277ce318b41ff62aec252ab7ffc1965e74d67c71ee38c46e0247304402203c021d7b8d62915fa40d8a37b4f39b2f23ea43cfeef1ff300e62b9ed9838638a022072972550f713adb53fecd4d64c859249c25630465eea7974bd522ef9e770236a012102149575104349948b9753bb71ec03a288c629c6233bb1bd0b859049668681cb670a95150002000000000102553e5e1feaa3a0f32e6fe29e4014b564ac444243011698f987ee07f3e86910500100000017160014647e6e2d188e869adc6e02db2c136bc00292b093feffffff67ace973d08b6dfab44a8f6f7e3de66438b47017f5ad6b9eb41651034d4a214f0100000017160014710d792eb81bcc6f6a03728fe2e382c2cfaab8f8feffffff02b8c90f000000000017a9143a4e26f511f6e34c4f62a0b0ae3f6477f8f933f48720a107000000000017a9142eaf6235a5a3476b1c6e2df16b581ab50fed4bce8702483045022100adb4be08529507580081d8f973340d5d4678fa13a96153f49b2a29a717b6129e0220435b3f76112cdd2a0702feca6ccc6942e6ac2505d50ffb3666a64db80d2ebfc60121029430d88cf2529e15d7765b8d9edd780259cac3ec53fe7690b5a5f8ec47b43274024730440220190eee68e61f875a0b8ff50d6acfbc3fa7afc091886ba20eca67bc0638507ebe02203c48e1c8ee521939f2c9feef6cc470c0beb1a25e99d9bd253c5577c344012f700121030ed8ac535bc604bca8e0335598b148b020736d6a1db285f578cd461411524a770a9515000200000000010238e1774875fc49a7bc20a58ce5b9825c69d6bae52648abc715eb2624d97f4cde0100000017160014b733fc4a6f1a7e3a7b3f0f2f40e64771552ac833fefffffff6acd9433e76fc68fbf8ff7726ae4da7025d8fe83585c025a31287769e50360d000000001716001462bcedbace6c60604d3c201b2cb3e7fd684b8210feffffff02e2b80f000000000017a9142cb3ae14ae02c18594a6f5439fdf3cca562f20308720a107000000000017a9143a38990d4b99e32ec727eeebd7763eae64abfa8d8702483045022100dbf895a908ac3f8d828791ce16544929943b392c9851841d4c53e3ee6c12799c02202f24227edf1b2958a047c0e79dd9c5abe3c30d1921567bddf31df17519d8a1bc012103c5dc042b0ba5145fa8d8904d125aa27b7d1f4262d5267b0e3433204be90619d802483045022100da937811236325ad3929d1edd166f0d59aa91b216d34463d719a41fa4c37230602202a8b0364e0eb4c8967ec7c5c94a1e71e4165148f286b9bd06711bce9d6cfb7500121033ade8f202b0cfbf62897b63eb9426ef229efc02138288d078188a2a4162fb17b0a951500020000000001022c01e935c2e019ed15432b3d2e8b97f0b94326dacd889017789e892b3287541a0000000017160014d0dcfcaaee4d9bf9b2604572d0cdcd5f4fd1e0abfeffffff6b6c41ce0e616c0f6e1ee676225ced9ba8d31eb1c27bae63d2a809f53e19c4bd000000001716001472cdbcb58b0114e29e2beb1d7a1299510058aa88feffffff02dad90f000000000017a91420e81e603cb3be66a5cc2b27d414876b3727f38a8720a107000000000017a914eee6146346496b042b9f62bf6913b5584d0ed05d870247304402204557265b4b3312e43658c95ae4d9df85eab3147503c4b6c1fd51d7a3bf9d9c760220654047577102b869a5782cc53b49f81e625ba85fafaa757cf64ab1d3a7fae2fc0121026b14542caba70cee966f1c7860a7301bfc497b00ed039f5b4b7ba04a235ac06902483045022100c27770e12d052ecb98289ec6bbfeb394fa2f0e3c676a45297d54ba519519f41a02201336ee5c6122d1efc81b0506b96721c913837312c2ae91e2c0172a74ce34432e01210267a28cf1fbf386c9b5824a7b25557e36dd8b06eaa93cfa2880ec74dddea483b60a95150002000000000102d65c112efae214608e873ce44636187f99fbcc6fc4664da85534b02bb288233800000000171600149fbaccce6ed29071aef294547a572d5fce6677a1feffffffdb4c9dad6c05f9ebcfd165df81d60854c17fe7fff51e0cbd2cc36486d0bbeede0100000017160014ed75e57c988d933add20555a519b34ecf6aa6009feffffff0220a107000000000017a9140ba6077981d45694f7fc1b46feefcde38aadc83387dad90f000000000017a9145bde6fbe7ccb0e68d7454f39bc452fa01ca138bd870248304502210096c9d540850af0ae3beee8071a15a199e458a94746d450980169fde39be9dca7022002257fbe3e1619af88c53502b2490db11e6acd76901d2bd47bb779c4ac40ee380121020d69defc91d6f3e0637a3942737edeb52c422753053917f2122ba09f7a0328f0024730440220566050b07088f42ae2674e3843dc4ca857fa6a9d3cde4c95bf2c1731950fde2b02201af63d8021fefaed33858a17766703aab17ca54a91daee32ba97a6895d1689050121021102b1d870ffd467223650f7eced469f8f01df3193e39a129d68a2dada5484200a95150002000000000102030a541ed85a1b816c0068bc5fdbc9924fa0a6c04484aaf7547f22ed7dc6121600000000171600146925d12f4d712f4b239b361e6176248c93412a0cfeffffff4abc77f3b8ff8899a6640eaaa517b9110a481b66adf49710496697b50e0b31c6000000001716001452b5bcba87e3d58ec63c9faf61605d624a397a63feffffff02becd0f000000000017a9145f58cebfec62c445813d73d19234a720639fff028720a107000000000017a91433c134f6d6e776de280b698ebc8a9817a586a4d38702483045022100bcbfa5700c7059a3af9e0850317352323ea22d90c06ab0db09896cf76c58201a022064d8b718bcee7c7dad93e5a6b6c60c7619acde06993fee73f3f1497288cd3f0301210330ec70104377ab9fb21857ac52aa5d327831be011e3d6eb3a1b00d5dcb8f77d402473044022048c5a3710cd4a1dbd66c230e82e742826a50fdfba3fb69314e21be8ee4bcb59e02202cc17e2ec788fb0c3d6f8af2a447b95a887a06d369218e10acb4dc16bf8c1c9201210335730913019ace320be1be1a7baa5ba1ab1a318cecfe33e7b0a549132cdac31c0a95150002000000000102096ecd09a2e047d2dc6047b4559b96082294c137471998e13c69c4c4e27d31ed0000000017160014fe082bfb9c1bae5cefb5c2f1672297ea91d228d6feffffffabe92e41e5edd140916ceb32f66accbecdf34a937ba3d71d37b7847dfbb75d550100000017160014a05d7c1f5ee0140aecdfeb97ec6eb20bf72434c9feffffff0220a107000000000017a914777471c01e7b71b3c429883e8760951e6b2e4647879e8a13000000000017a9143b8a1cd5508f12aec96eed4b7fda4e7f2723fb2e8702483045022100a342cdb7f677e98378db1682f6c02f17bcb412968352c7344574c2b9d60972fd022019d3cef0d3774a38673b13634a0c8e49768404fce98aa5b7c04e0d07b727f2ac01210350e1eb934c12998ecf375fe8ecf8f4d6c586efa3fb4ac30357aaa7309e2bac4c02483045022100a665c4f72c21246301cd35107c8a32eedfa946d5c0e25f16862fc0be01752d96022068621ffb6d1b0e5e7acfbbead186d597f83f5434823853807045a647d38fa85a012102f032036f5b9e60de434478d756f960e0731e77521bed08b093fac2a1b4018f5b09951500020000000001024424103b72d66db4d9bf129fc167a4c980ca17d323e3b81f8d9dbebfe581912400000000171600149c19e5f5c546888fbdd264dea3aeb633b519456efeffffffa9a0a83405a84c1b40b66694bcd39e12ee9a60971f1720c60e7936b833e2069001000000171600143961bfccd2d83a76d2a74092040081800cab1b26feffffff0220a107000000000017a9144adc43721f9ce7ee16cebee6c2c9ae56a53b46ef87bedc0f000000000017a914a20e16290c07955e47be57d18a6ef4bf6d51867d8702483045022100b0d6a63b8ec99d1fdd4318f89e317e7e95bebaffe0b187931ce70176152cc837022059d2e2709b9690f6e0589dfd8a69c4ef7d75d080e4ca5e73a90ec4226a5353fc01210296cc51c0abbcbb56eeab597821ca04eefb92b2608bd8777664eccadfba796c590247304402207a63761197a51cc7aa27654391583304941451ca2b06fe5a33c08ee081fd095902202ff07ed215c61e3176bed0719760ad5ac77541cac841592580c851c454fb3b82012102d638eb832e0e6da80ab1174021186ef72ffba1203b0502f2e27908da1fb03db70995150002000000000102e5462cced53d48c32e9cd6902a8c6f709ec90d3cc10e08be8c11650aa2d9924e000000001716001449b5e7a30900db924a06d49e8ade72520f6dfa1afeffffffe5462cced53d48c32e9cd6902a8c6f709ec90d3cc10e08be8c11650aa2d9924e0100000017160014f501db0da119dacf070e84c43b89181295fa7caffeffffff0220a107000000000017a91468ace29c08346e844feaca3ee8deb12b510c651f87e4e30f000000000017a914883e17cf484638ba31f9827b99ba650aee020cf987024830450221008089a600d4691f0e7f70fc5de9dfa0e17bf9eb9e872cd254a5afd371ff746aca0220019025d3c106054a9fdb69c5a3c2bdf103ac633ced40975c90d10ea7d0ba343f01210360a5ed03bc3269650339aa9f1f8315c69d4891cc420bd47a42d41904e9c7252b02483045022100b43846bf3c5a57163eb4bf029b702a43be3026b7e7326867f114d91e270300e602203f20b03414ed8600832513a7e9bcb03cd053cfa227fac016ec98fee578b3388b012103498b88a680c5db48bdd114c727a6fa48cbf506975b2ebb4af539d000db86ad4b0a9515000200000000010404198ee2aeeeb96d4890e5404f149348613d08b5eb60ddddf3198672693d970500000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff2bb770e8813c44054ebdfa957e419488559f747f23c397f19bad710edfd820950100000017160014e027c50359961c9a905ccd4c70a34c7cf6016c8ffeffffff6ae6b305a3f7a270e5542201a91e4590a9fe4941b5e3d07d467f5f2d6ddb10aa01000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff87348fdbd13b6c8f59fb88e8937cdb7caba86f7cc52ad39b92e396db4d9eabaf00000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff02c4e50f000000000017a914240332e3d2fb1c4ec9f07f4bf8e9d8e121d6e0028720a107000000000017a9144bf2264302dc0c9de18f855963d4940ad83b16e7870248304502210086c38ecad257fbe35a31dd9f5f7b72cc339542e8b88bfd42dc1b9fea322387d40220194d66ef4e96f04295d80fa03b7228e8c89f0def0ca8737ab4ff688357cd3a80012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02473044022062c8f774f4bcd11f6dcf43118c4d675504e362df3dbd81cf5ce07643d2e5e144022021b6dafb62e9cd72466aff8a0454641a2729faec6a63caaa1ad26b2c25e28840012103b009aa84e8c1c806f9c571b26ae6aeb4e08e4ffa9df3efad62973666ab3fa92f024830450221009a4d26d9cc41a4e26d8300d410bfc014ca32ca3e74440a1a7d09b5315698cafc0220618c739c9abf6e9fb5b1e6ceaaded47b9900a51e3da30221d3cda0c87727069f012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02483045022100b7e08a015dff74469143f767fb0f0e9c98c9f4cdd0d29baa9e235d02b37a56cd022045b9294f150c9826ed9dd47961bf72adf006371fdf84c56e7cbd672eb7267937012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c0995150002000000000102bf7f7049392937019a03f91e86f5b57bc4a4dda431d3234a80fdc3d5f0bdc46301000000171600141e4f4896a726e1b3999d109d6df1f32b3a38bd37feffffffd572a52ee809171c4dca18e48c786e2318c77cc96c93bb698d8d9444441861e300000000171600141d983cf6c5ed4ae4cc893ef62b6b7d6a28820b4dfeffffff0220a107000000000017a914dce9fcea77a48a129298aef0e05fa4c0383418158758ca0f000000000017a914a700f0f0708fc44d02a4ef3d5e32af164025ce198702483045022100bed51b5083d25496a76c10f6a99190c83189a74983dac098c8dbc2abeef51d19022031e39f80eec08f338ca48f4ef59b6316ce7ebdac1b1a63eef270db5707aee4f7012102239576ad40adce50e93245ae427bc67cb40b9b502b016dfc0de7a8ec42038a2e0247304402207a62fc1b61ca4a9e8189b95937f308118e178c81ea372647b6c6eab9c392eb7502202f7ad9a08f837c9618b9890841797d88db496f1ed04812d18f9b060175d29bf301210298f91e6b54a0cb8657e9ccc2e6f124a474061ee77e33bc438ead9c4adbcc68d10a9515000200000000010216534467a4d8f904c37de54135b3b7b2fb8e8e9c2b870350a7f4ad94afbc4d6701000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfefffffff178adc2b23e17e4583fd7f3da4f33df77dcd63b1a769cdfc8b7f0bd00f32d7d00000000171600140528e2596b13e47e36c980d01561f53301afdb37feffffff0220a107000000000017a9141497fc698ad6967fc3899b5a6fe060f965200b4387c6c50f000000000017a914e4a0e8b4557cad1394ea80133fcb69977bf482998702483045022100f2b203005139d2520bc10d3c250d61d7432ea978d279a0e885808b30cf3499a102204bc35be33807fcf48afb69c09d7ed6e352323382edd02bca09bdee5e88022d7e012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02473044022015698dcd36c14bc6b954367851ca616bf8e32a0a9bbe4c993e6fb20b10aa59a9022053ed5b6fd5cdbb9ae7b65e9c5944ac7e0cb7c5dbf9a979c62df4d3d0de259bfc0121029b0830284eb717d243c5924590e6c05181c092b4d74a6d6af2000d44dd45fba7dd941500020000000001026169f2ce6b8255d3570f2f532b37b7c51367d686cb8bd6c00e4caddcaa3f2e0e000000001716001457744ac5c3b9f57042c0b9405d374e39ed151550feffffff70e26c73651ccc9a6794e5883f6ab1b4f6aebcd81cc796a6d33796f31d1f0ed401000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff02b8c90f000000000017a914ccb3eec45f620eca8a9064eca847f16171cee08f8720a107000000000017a914de09451bdb68d2995f53a0d45922bbf56556da548702483045022100bf7b583f62629f81a227629989d0b27c7083d39cbd321b2a6b72b144bf24942f0220303faf90794cf0032ac0053077de80b21af1cc888895740fbd760267de6e05d20121025a262452fafe5a78f5e0d52262f599443d85f4e63f11f43b6ef4972897c937a20247304402201a738c48613e2b5794dd0b5599e5f1bd1a0d90e724907ceb26ef8f51e667218302200b66adcfbdd305965244f3b46a71e9748b58d169e1b9b2c519e6b44a57813184012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c0895150002000000000102358649d1777b311ef27ccf811ea73d6db700acf670b4721c48a26eaa7c73595201000000171600143572f6df07032ff83d741e132a91a7a351e12f2cfeffffff553fc4bbdd68da2c9ba460726fbc8b7fdc752077ebc318b20615c567326bdd2c0100000017160014163760d6a909ecaa1b6ff13a05bae15b9af375b4feffffff0220a107000000000017a91420aebd94106b26d2ac6698bcf182dac99087440787c6c50f000000000017a914b57f1bca92c54a8589370cef4fea1204ed7d1bf1870247304402202fccc32340467c2f0a8caeebdfdf4a9f77daf2556d8f6a651e4bbef7d4f83484022074e96ed340c2e3f8effdf95e016f4f2bed8c510d6fd6e3fa4da75f942dd281d2012102a09afa4dbda427eb002ec54d650eaa5f4d40bc1f0fdb974eb2208e425456f40c02483045022100e489d951b0bb04811d6b56ec3361007e1c2947836e47ffcc2a571304be00b97002207a051f4e59730b7d921ab4f5faeb109e63a710df7d662e226815378a3c8facd8012102cba56b17d1ab737afb930ea43897433a297253f803e66a519f69e088b252c62e089515000200000000010211558c01dc43eaf4937488a28cb74442eb880460e6142afc86858c8988af51d100000000171600145de4621b1a9a88d71648ecbbb38cbebae8a6384bfeffffff22bc2efb84a989e5e55b5d5fb767d210939fccf5132679bda08cec6f1b9aed4701000000171600149d6c8a3994a54bcc7fb2f8b48a047758b79fe362feffffff020c2911000000000017a9140f17db795718252446f4c85c482cbd9bcb66338a8720a107000000000017a9141edccf0690aa081c4e6e54f6de94a94e6bac0ded8702473044022007fe46b47fdd45dd3b9f842a800b37c4ed37ceb0ea0993cb3697450e610020b802201c24f19821646d3aaab87f4a5007abde223b8f88b96582ff223a362c8f08c8ca01210367afbcf28d05afb8878de1f1e2381eddfe2574879ddcb37bec244a402a9fe22202483045022100f0fc760a332409d9ab7bf3a1af69e872e6a56112b7c26ded69068b8a8d3ae7ed02204ff17e64b17a9c1dbec822aa3e58246c96c7dd2eb1c562732696fbae6d79ed9101210315273e25f5a5add42ab2f9529cfdf7b68ab795898f19744007cc8360d48a1ce2099515000200000000010117f995d2497ad408548da0d93b90c9f103158fe3936532d8933f640a81062dc20000000017160014feb877dce5105c5f48b50d45da78cc3bc26c81fffeffffff026d4d00000000000017a91477d59489c39744948043e46866b63a42509e75af87204e00000000000017a9149a452357e9b042570f09101aad459f8c6293193f8702483045022100b7a52d6ac36e5680a97207caaf6a1605c4b9ab72ed45bb2849256cd291f6fb3102207b12f8dbcdb36de6c5486578af64bf7502d6b4218ec642213a3049959865d1dd012103b0fa4b9afd01aa233409220b49753e8240c1a4fdcb018a73431155481e7d966008951500010000000187c8796980fc36340eea366f0b8579df90323241572b87ff6b428d43c5258aa601000000fd6801004830450221009589ffc8ef45cd7732f567f2acb1b65b2c6123ec73cef35b28711d27e1052e1302200a69dbd42a94340ebc565ee1bb30ad688c80acc0e14c8c80411c981ce705ccad0147304402200a01e4e7e0b862391fbc96e3032468468642117aff1d3eccd7a7e3debd44a13a022068596b1efc5815850b051a25d6708341b04331378c25fd11d6416f694f1cad0501483045022100b1c817924bae6da9dc91774992c1dade640cd0ff94c38ac134c9c964e4d1b6d602200f4556f10e4a8fdcd7aa8a8f0fe2089dbee78a43d5f596e304fce227cd1c0773014c8b53210315fa23eacf45e4977303c1abd4e66c6f3b235488dcbf3823c69d8e0bf648701c21031b97164c7068ce5a7a7d420a4255cd9149114f26c863c04d79d0ec25bca5fe8e21032352dd4af510595e2869dadaafc7afc2897b3d491ac75f51943d86e2e5a2ecd92103f1215f71ee7562e9041738784d9a8a93a96d63233658b4e1e877a2420a5e630d54aeffffffff02e80300000000000017a914262af7c73d810a2d54386bf7ba25afcb407d26e9872c3200000000000017a914a7701f78ceda31637ac5f89b6c6882f7c449fa4987000000000200000001b00eac491bf70eebe78991d75f198747edbd7c9ef6576019b0bddb9ac1c8816b000000006b4830450221008b007516c615a1b9926d0fbb7c2c79db9e17684c70e977e09b70c84904f5cfb8022032788dbd07a57a93b52f544b1cfd95b3fbc3d49a41ecf24f46763831cfa98d3b012103ebabc0ddc28b251fa7a4254737cf15ff2c8c19a81b411d3b5e4763a66d365ce2ffffffff01504cba0f000000001976a9141ad156cce79276765621495e991679586be79f4f88ac0000000002000000000101c3e6a902f42e40a7a12fdfc804237ace924b2e37168cfec8d6406b9e0f9c861d0100000017160014a22bf86e70808ecc04b588f00cf8ee6e9658bf7ffeffffff02b66d15040000000017a914ed7cb2bf7e74cfd8d9644a5bf53d5d12a80a969c87204e00000000000017a91410ba8d82630c65d4cd0ca08acc57e369845192c0870247304402201e91eda6608b73ad286325ae3497ec6986d8535ff39a5df6b3657cc13b4469ec022022e64ebcdb6903ba0a7db21b9c528248c57604801c3f84960ef507bcafcd9b73012103cbfe4b1ded0eef381ddee15160816bd6b4ef9f2f829c5fe8d41978f7f38ea3cf0895150002000000000101ae4587ce8354aa000223cbbe86c0b597ad5a9bc0a475597cbd9b84aa6a301cbd0000000017160014c9a25955b47dbc10c298b951bc8f264d701276c0feffffff02204e00000000000017a9147b83ec75d37bca215c491d4890e77546677b9b9587e31e15040000000017a914a5238e35c82222173bf9334a6bf32a69a351846e8702483045022100cd35fb7a8a9251e38d0315b7d43f91fa4eabc751559e95819637238579a0f8fc02205b6e491c1f22f5a50f67743880dd6616352ea917307af55b82b72ec2b87d5710012102db0cdb2810272d2a4997f427f5144437a9ae41359e5325288a5609f5995b9c5c08951500020000000001018850bdf794b06ff3b930f388319b1f9f9014e52b2cbc3024ed64b10be7e903800100000017160014e7c4df8d7d013fe37cc2eb77f8c145391b63105ffeffffff02204e00000000000017a914163ebf3a263a4ca04ce10d023752cfa11bd5e8658710d014040000000017a914d171e660b6a13279a9b25978579f16dbfd16a86787024830450221008084f254249cfc53792930ea2a12c1e5ba47178861d8b6bdb9290e4f8eedcb9502206d11ceed9f9ed4eca5062973be5b89ea4d376a368a0efb2c436b5e51cc2e6da6012102eb20b0a4d000ba6e6eb394ce488bf7fc8bc67f7480e276152c2145f57373a81e08951500020000000001016e027770bb2daea75385c7dc676b7421593bc753ddc8aaa55cb0ef91120ba69a0100000017160014bdffc7c2e2c4ff3fd15f70ebd524fec493dc7d53feffffff02204e00000000000017a9140d6989f6b32de0d9b5296d09f6d9556b6f7d9faf873d8114040000000017a914caab96a220dd0480006289c22912cf9b948750468702483045022100d229828b8efe3e78372793ffdce891df240dda8cceaa45670a2ea0be0ee5e303022063494d0ed428b647a8fc393ef5b8e97c3471513b618e4a874abcd451af08bb9e012103fb86532aefbaa7789dc42b06bc9de929ef89d7c349227d36c6739fd8d6eaa5fcdd94150001000000036e027770bb2daea75385c7dc676b7421593bc753ddc8aaa55cb0ef91120ba69a00000000fd670100483045022100e73133c635c14b1b296074a47284d5f34949d4cd42d923d7cbf5c6a77456e914022077212587fc2a0244306f00be76acd7c8e79b0648f9670bc4f80b93dc2595d237014730440220630ef2c0996e2016b99ed707d88ab1a75746818e2c0c72fa5c40b7989b37e83102200aa2b5abb411be880a0b04a39e0ab42dd9e4eef00cf4513ce7a535fdfbe6882201473044022076815d0f3bf70a65a53132841e9ec1faecdf6dea811ffaae161382a8fe9654b1022025745eb1b42d6d88ce2a715170d9cab48f4da0824ab34bb91869d09cdb25fd9a014c8b5321023a1269bef1d11ee087c0e32cb7189660f4e8542fe16d13a19c898cbbe5052a212102af8673748e8c6e6ece79d15580dabd8bfd04d4b5505917b0ed231b97d1ce5cf62102ecda51500dfe9dd1706871647b60fed639289563f41f622eec4719cd342780652103f89e5429576caf6c629ce1146353b37528652503851695e46ce2685471b1ef4754aeffffffff8850bdf794b06ff3b930f388319b1f9f9014e52b2cbc3024ed64b10be7e9038000000000fd660100473044022044fd6ffc8a870b73ee267099b7ecefea4d6eb6ba57aed522957304d85a5c5766022071e69f099db9d26a83755d9ec6dca3d989ea25c7411a4d01d19bf7b4628896250147304402205390de11ac2df1e17a8b327618a3f3673036a3ffcf6414d434590ceda57de4410220186d5052aa322d77f3260bffce24b66f8804b586a17cddc1ac5d230774a3296b0147304402204802cbd31ae239737a7e49d18a70867c01adb050f18c8d91003566a746596f9f02205f0a16877d1745106bcb6d793deaa570a65219ea13a1a8e7060886c226d311aa014c8b5321020bb4b97cb91faa5c8c96694d0b96e546925efcd871a70ca59f97f990b601fae1210242424ccf02ce61a123f92b439b81c2f515cd6de239df178b15b41702456c8a9b210373fec08f2ee1bbcb993bc0f9a239da8bbf50f53da1d1b5a4ff0f32cc769052e22103e7593713dac4198cc6a957fc84973534484a190e6a00446f7808820bce330b2254aeffffffffa4273a3ff1d86b8a386aab909a0e56d901e70dcd8e4135bea2b76161b5e207f900000000fd670100473044022012b205e49a749345cb3f8d3989a2252c1ef9a5130fd117002ac86b828adae89e022029e9b9f6cd672645562575c85eec7a71125be6512a51259d35659943fe65ff1901483045022100f17b2a25dd642748a7d4598fa122ac2ad65bb2458d23838994ab55d864b09e280220688303d2cff063d142b2e4744a435a4901653de005d96791be996ece4852e76a0147304402202ec9f286de0dfcab14011ef508b85339cbc4d80066806bdbb3c602fc664cdb630220764eb8231e6e22ba9a86258474d8c03fad2a76bc0d8ea0a288bb4751cd125762014c8b5321020511f3bcdd54e783cba9cdbf8425f7beb80728df1061ade58672d8217bb7c62521026a524d5d35633d63986eef966d7a93c8ebf18b80df94aedd6ab2ddb2b230e6912102866c33454469715b5a09417cfc7f2b01aa9c8a2f1ad21a935fcd4c1e17b23000210304791ea71eb21e61977ff269e2bd8d51df47fb425934cdca22ac90ca9334d3b654aeffffffff02409c00000000000017a914262af7c73d810a2d54386bf7ba25afcb407d26e987081e00000000000017a91455532bc8e5ecc2dd5ab9e87d02bdecb49e54caaf8700000000010000000147231f057d593424efcb513b3134902fc37e74cc76061c286151485f6086183a01000000fd680100483045022100ca445c926ce7c709ac1470d638ed57ae88cac642cd059d4dbab4dd42efc4d6f30220674f1ce4dde20754ae69664cc6aed3b0a6162b1ef09ebca00b5c6f1fd71f603801483045022100b9e6383454298560b75967500da6970f66f8ba944c116d7f08824103f09522f502203f77b54ccf8b19390c41f075d3e3b47d087a67567eea962313b658b7133bf62a0147304402201ba6893f2737f7256f4b3192b791510f91ed11e3c8467a5ab75d4daa3979888302202408b9247a16cea5c9547a6f08475fc97c46719c01f0fbf5f03a7fb8c769f605014c8b5321026d2a508a449e4e10fb9cf30988d8ebf7819dfb15e450e2ca78b88480b0293f982102ecaac361018c45764a69a420e454a71fe0a75f69e525b671666a530c80ffa45621030aa6be45275d7872e09a3da8c8e53f66cc93190bfd41c9d2964595d495a2b40321033b13d206c8d0cd638b0da9d5c15e2c5c2d69a6e359db9c8045a96b65b92734e154aeffffffff02e80300000000000017a914262af7c73d810a2d54386bf7ba25afcb407d26e987140200000000000017a914fb45e5d1a6e9ce51786a502ff87d56aed2e069b9870000000002000000000101fbe7d878560f605f7f11071e95747076e08a3a0dee8035b85a5df8cd54601e0d0000000017160014feb877dce5105c5f48b50d45da78cc3bc26c81fffeffffff02204e00000000000017a914b360817e83d0fbd932be96c85430ce36bb629910876d4d00000000000017a914ac3c08bcbe94918b3ff94a4f0d22b1fbdb697b9d8702483045022100e49c3f1085efcc451b8ead4e17436ad841162907a57f61169dc13066f5fb29ff0220276f75452d81cafb70f7bf5e4f6ae8659d5383eb20b7cf1f8f718dbe281eecf8012103b0fa4b9afd01aa233409220b49753e8240c1a4fdcb018a73431155481e7d966008951500020000000001032eacd72aa50356c507bb3107266592a33d6f50da2404dd625c6db4721bdd24c30000000017160014feb877dce5105c5f48b50d45da78cc3bc26c81fffeffffff7d0230d39dc5e8845f6ba8dcb3f6f9a1a93601c309df86325870159d5ec7440e0000000017160014feb877dce5105c5f48b50d45da78cc3bc26c81fffeffffff8785b2a1b6fedad5829f4ad962796416376c60217edbfc67e6718efb3d898e080000000017160014feb877dce5105c5f48b50d45da78cc3bc26c81fffeffffff02204e00000000000017a914448b6582ae8c7dcf89c0c0a2f0a3c8ac39a5a4df876f0200000000000017a914650814b91e2bbe7093d773481bf20aab5b04b6cd870248304502210092b91f5748254c495ef596431d4e61030b31d1ef7ab034786aa9a8899145ed8e02204b7b2e63a2f89f42b63bbbb659dd1e0030404297a20cf652cbc0565e59225667012103b0fa4b9afd01aa233409220b49753e8240c1a4fdcb018a73431155481e7d966002483045022100a8092ab2a98384b81914ccda9e0e5efb81da3a02d696675384e7fbd154470f6302207c2997676f4cfe156893fa935334bcc7bf5a297171f68cd7ec1adde67095ea38012103b0fa4b9afd01aa233409220b49753e8240c1a4fdcb018a73431155481e7d966002483045022100f5528d2eee5015629fd3a92880254dcf693c83704ff8a905796df636e34ff2fc02201a2ec06f7e72f83a5019949f47f7c1aa9a97edfa03ffd432296288a404ff25ed012103b0fa4b9afd01aa233409220b49753e8240c1a4fdcb018a73431155481e7d966008951500010000000352450f2518e4900711a5f5a6b24e630e97643dc9184241a5096b86c547e95def00000000fd6701004730440220162108f54deffa24763dc4d888de6e24fa965b66d5450d77b7784f94adb903cf02203d15a8d5a5f7587094dc047e4cf10b154c7690bd5707d0d23c6189e57d45ed1c01483045022100a80063361ad2601c7fe0c9f95a9f7d8d822794ef3de9e70b4320d8f23ddc6bdf02202c84014c915674c7e8ea84e2ffd353e1b106fd843c16f7b916d0fc4ad2ce3cce0147304402206f142c477946b0d5edc58da3df33b0005b483d52d550b4c459fff4e5838629ca02200bc354fef37f70d1fa12cc65ac8eef9e44d1c28ca172a2384d916730c851e348014c8b5321024496574d2587397a259e4fe2262786295af782221a2165baa22a597118f5d72c21025d0dfc6cc47ce7934b8a9784d8e02427209333cff918e9be1372c8c8cf6caa192103139ad7e830f54308ce1c59c1c896a25525bff78adc43757cd348d28a8629b0be21034f8146924d0ec6f9d377fddea947211227a199f8601919c9d6a165b537e3a26754aeffffffffae4587ce8354aa000223cbbe86c0b597ad5a9bc0a475597cbd9b84aa6a301cbd01000000fd6601004730440220356911311541bc584a8c52482523bf1b34001b563660cd71d294d50794c4becf02204eab00a74b69376368f9c7f8e464df294bbbfc6078b031085b8c266be28066350147304402201c4ce5dacb3de4e87adeb4053493771a3424a3ff12c7be6f9e4726fda74f5c84022013bca8d12321d114d93a413c7edc779da955c6b7f2b78b2e5d7f562cd67a8f4501473044022056e33bc043a75e69f4febb7004da2ea5bca00c06bac94102172aa36f6320ce5202206f9968992a760bb3a0031988ed5765b8b74c64c26e13e171a23529ab37037bff014c8b5321023f22bee44de184e586b5294ecd2923dae3ae8a162fba6a66d170697b77a4223d2102f29c0fcad57b7e41167ee98d222123a8d8548c61c1388ae4ba5438fa23b9946521037b75a2f5f26a5070778bee2e898409b55b07951fe3887ab5e279c980132031be21038dc7a137fcd308177c441f0fc7b71ade6ee78bcfc9fb9237e180e6cfbbd1b2c154aeffffffffe1acf3b5c4e66363bbabe9b069ffc4eb092b82ccf2d1089625564fff91b7a44000000000fd670100483045022100c0cf9e03238bfca61c493ffb855af273a6e130286e4d8a7147de3903b063aa8102200361ecd600bff195e8b90c8725037ee8e37d98937e95b7d5ae8249f7a8d90aa501473044022012b1e59c195a4dbe1377f8a7d48a32ea10fb1dfbe52a380cd6a00034cb605f1002200d6792c4fb30524e06a22875384d6d7bb97a9649b844fb9208dadbf8ccca62cc0147304402205be27e7f52a652a6003207c83b12add0f34ed1bcf72005e0afcb5215c0c8d90e022054c100d63f48f8c45b457a971e2ba7a4b033a7eea38eb2cc9dabd9be8bd665e3014c8b532102118509aad57a5745aee6d5eb1c461c4f17b192083bc6596418336c296a427e8721027c0b4ba1883dfd9d4597d3a952fe5ce2a4db6d2d65a22998206809c8c4fb000c2103a2beeff2bf1a00c11256669aae89c29fe82c6a31cdc5bb46766b4a2758bdb1d92103bdaa7eeef9ea56c56b8efa992f35cf3eaf35847ddc25903caa06209369b8afbb54aeffffffff02409c00000000000017a914262af7c73d810a2d54386bf7ba25afcb407d26e987081e00000000000017a914da5ba5c363602f7924f6e788f65dcb13ae67b20187000000000100000001e683586c4bc51fcf50eed67b673eb88ba87a6219d3b8ab49b6dd5730169bcdae01000000fd670100473044022065332b1dbabc2276390488b5e1d56b2f76c99002feb5491755e9558d435ffef902200fdfee16e412bf9469a1f108fde6a4a2360daee28df4d23201efb7ea37ae9d49014830450221008afbe2c18e2d250cc91c70d2c51b7f95e8b21993b769def795ab3471c40fee72022065baae20585b2a00fb8c9d8a16d9e0de484b228aefef489bf6b22b5ff30af890014730440220120d07d4628d6f780704881916b8a2ec26060e35e6613dd5f12904fbbf408d2102204bd3cf56b29283d25c6606de3753647332050acf15d00593ad2d4b0710bd5380014c8b5321025c0489f6eacc72704fbe12757e90c139395a64ecc058e232baf37b246d98713c2102c42eb4130172222bb4e6d23227c19f1a69ac111467055613a1f9b87723c10d6f210325e2983e09942f084d870c2dc9c85fb6f34667df2b769f83b15e8a65db1c8fea2103ffd4b01f8cfd4c1ac1d8d6c1764f989d8a5d7d0a6ab096f9b9ad0a7522a5718654aeffffffff02e80300000000000017a914262af7c73d810a2d54386bf7ba25afcb407d26e987140200000000000017a9141768365a078e65567a56b903d0733dfd69c1ca72870000000002000000000101a3dfc1369175128735fef7f5907adf700470ea243ab4506ca3dfbb1c4d0b438a01000000000db864800122474c00000000001600148e7b93bc4e0a3bc0e46e14df61356a611896d27b0400473044022014b21ed88ec8b8b1c68f54a9d69d8e233b5407bb10254c0ff49a4c7edc48a53302206ac0d7e1c82f17fcc270f55a7d117fa3abb712df5ddc85c78467273ab8a8e05f01483045022100a10db7a733fd2a3e1699f3e6e736ba0ca7d49211fa52429f32a232902612eb3e022021f3217a9268e42a3e0bcfe90e1a757b5f53b40aadeded08bf4043e403bf690e014752210265126c5a8d24b34d0a910fef961ed918a6001b59cf507e26d9c50e7f2c99c2a72103cf82a5f35d278114e14bc64705f0c024ad3feb03078d02ca442ec8e0cd2cf81252ae5e698120010000000174ebbaeb6bb9912925df5362b7ae2221ac9f6e9edefd0dba60160fc01e3a48fe010000006a473044022007caf3fe141f9571c4d00341fe28a80895c5f20f908b41988e0ec79c4b2e880002202cd5c06cf58e837792e7a787bbe7047c81daf1868c04555983749edc85ec038a01210367eff3f2fd502cb5870c366db1e9e161429002c17dff93cbf26262a2dfa9c395ffffffff02b2b000000000000017a914dae593b662b7e8e54d522ba21949dd283cda3c358759690000000000001976a9144761e818b079040184c3303b0c6536d4ad86c9c188ac000000000100000001f77c356c2df01c2a7892310a1b7e33349f5e3270cfb834cb0bebb301d91c4b09010000006b483045022100cf6240ee2e9803a2ac7ffa74c1f9763683b724d20362ce7e02acdc1b4e542230022046990afaca5117db8f635b5593ec8d1c4571548f471195abe14c6b462c5cf6eb01210367eff3f2fd502cb5870c366db1e9e161429002c17dff93cbf26262a2dfa9c395ffffffff02061b01000000000017a914dae593b662b7e8e54d522ba21949dd283cda3c358796860600000000001976a9144761e818b079040184c3303b0c6536d4ad86c9c188ac00000000010000000237446b686dfeeaf36913cdb716c4f2a1f29b94fcb2c2f33049c915660618ef6a000000006a47304402200ad13a046e448c07ccbcdf73b884085dab999cf659046996a9f74e97dba6a04a022020601f50e0040997ba6024a8252bc49375cda54bef5e3d904bd1601b355cdf0a01210367eff3f2fd502cb5870c366db1e9e161429002c17dff93cbf26262a2dfa9c395ffffffffb3e21d0a429666d3def7129529443b6ccbd71c72ac98a384919671acf609da44010000006a47304402200ebb63173f22810133049b4ab0c883b9882b0d4d03f7cd9a8b21dc70b0f283d7022015f4ab8cb96881a7b85b4b309924944e18451d36195f87e55ab32fa3acbaf4de01210367eff3f2fd502cb5870c366db1e9e161429002c17dff93cbf26262a2dfa9c395ffffffff0238ce0d000000000017a914dae593b662b7e8e54d522ba21949dd283cda3c3587fbef0000000000001976a9144761e818b079040184c3303b0c6536d4ad86c9c188ac0000000001000000000101067da8289f88be4771778535766f9a3a4a85e5b44e0c0d73efe715df6e666dfb0100000000feffffff02400d0300000000002200202d74f4bc746f879340d9c41be37445ffe7fbb45d8eedc04097452bda5a7bbab572f228000000000016001456e0e81b4e06ef2371a36d5ffbb05df490d93dc0024830450221009df4784df36cfa15ee0e0b19cfef226f72d3bc903340e874b0c041fcaf3b35be02203e2d5981eaf11c4f41a4d16ef2db6c883049e62f4fbdc7c4f15d3632e727e719012103f0b499f6f03d6e729b67d1e18a0a8ddcc90d29ddb200c5e7be6a3f6867c9fda40a951500020000000001012bb0d19bebc4c827ead1604133940ddbd08dbff545499b22a825c07d433de96c010000001716001414fd3190d79b64b73ea7bf62350848fd6ca8f79dffffffff02a0860100000000002200205c90f984f753731f6e263e5a558f6980f80ae243c5e97dbe29afe500b725208827f74e000000000017a914733c3dd10ab45d561c23062284d8db0f8d91a37d8702483045022100a2a3fda5a3d3b0e5b35ac0455dbaef6094e3c903aacd96fa7badda7d2f31d62d02204689d9af6f058c415975e5814159bf8dbc07f565955d449519c786d6cb0ebb990121021c5897763bb703e3490af1f2f71786016b9c35877006234d24d13a7e37eb99a1000000000200000000010179a0850d548def0c0c7b6db2fb0949d65be71198ebb73dc85e7dd9ea640160a00100000017160014fc609af049d2b0717486a7d2c49d20b0c12eb5d3ffffffff02a086010000000000220020f2b64e1b8683653264a49982484bdf98f5d432438eecf655d8f8c16c4a12e20fa5694d000000000017a91438e8515fe3e51e6b08834aee7f9a7339e6b94e4e8702483045022100eae8fd3fab99cbc262ac1609eaff7d6c006efc488af443a0fd82c3bab27836cd022067e6ca31ccd8df381a6f7e0fb44283043cc05d959634717f53ddaa88145f1a0f012102fe9473a8580fc49ad905e79738b8f76319a1ebff2e3e3d33498e86761d71e7d60000000002000000000101613f858bab038f6a39a40abc026202c4ec2e2311e3fe329dbf4380731d6cce12000000001716001488dc0330eda79cee0f87c6fc947d0b31f17d3a92ffffffff02809698000000000022002085eb6f3c520f29185e9348d4d37bdf8cbe254f0eccbddb4cb8f860cfedf44b872f4d4c000000000017a914b8591cc46348991d7d1d2fbe582c66d4d6aafe258702483045022100d2b45a10cb11edcadd4b370d03fba357cd021cd6faf7ec2d89b802889dc6ec6a02203900eaacde27835329d36613f62c47263af6b3daae5f9894509e61f03107a4260121032495bb75903f2869785139bb7b809824fc5c669083b08b029df623c9f88dfc5100000000020000000001018d0086eed8b653ac22cccaadbf17fbcb61651e42870a4e1d65a2850fe9407eab010000000000000000015ec689000000000016001493fdb1718ee007c9805d5affe2dc98291301961d0247304402202c010396f590989de04c635f5392687b4e82254a640f9dfcee28cdab130a395402206c586d8f5eac66e37e4466e7edba3be9cf22bb01ffa6a6cacf6f70a6f1a0ea7c01210210c22eaff9d805503b41d803e3c0b6124dc8f469f1a0a5733567cb22d63114f00000000001000000000101b4fd52832bcf02d69d9d65d5be50bbc669d66ae54347ce38de81dc7f47c716dc0000000000ffffffff02f5582d00000000001600146ee342def0ccb686fa7833ded90de693710cd36e44685c000000000022002024fa65d8e2233da364886ee2f900c7aacf7493422eda7e751a17138e13a88b3b02483045022100f503ca802bfe4c356b5fd9b3ee6ccf535eb4aad0217d0b2e976b0f09f66fd8fe0220188d33629b5468f8ecb4f45dacb0b4f529c90c9f06ae49aecce4f20782adf97801210212235e148a114f39ecf52bf9fcddfd177337750112b80f86b3450695dd5e9d9d00000000010000000001016ed015c8babf74f665c5146b516b6f2c0749a9ba81708889feec2a2bb1aa61bf000000001716001493a7f4fc2f47bfa406fb47d7ee4e795f51212661ffffffff0212234100000000002200206f1135cc7d04a4b607bbafa081a0d0784bd0c7f758739e37e5b785416461c621d291a30000000000160014e744177aaab67fed0290ac119bd1361b67bfe91502473044022072952b23082dc7a1f235a711823862ff9438360e9120d791c426f9453b8232d6022015d34563104a1a6817702d54571a740e2310ea31362851b3d176c9ddaed271ca012103268f5f09a5d005f44b593fe9903a3cbe348dc7fb0e48f64e0bd6e2689185be94000000000200000003352151a8b61da7123113e3434be1873fadf7131f0319a1652b1e193bd5a388b4000000006b4830450221009685a93ec24bcb70c0d10b0f7dbb0ce24c25dc5b1c962b69dba8094b06450d65022067596fbd987e49739f2be9c6bfa1ee3d5dd1aeda03500dbc60cc2ee165daebc901210397216e1fedefa6ae3d33b46dfd8863317420a6ec97039c6276abc14657c745a7feffffff9b12a7d7e6e4a4d8980be3c604369eed94d28af27ba405ba57d2a656e0057625010000006a47304402200b8aaafe4349bbb1d59e5ad9421e57a286b79a0515277dd8a01fb83fb1eb983702206ea02e6414a31510ba8c0873279f279340d29ce80ad00f57dfc3d15a58e9af640121036fbc9941f073aaadfb033e9412e3a15ba966dbeffc34099023a1ceef63f0d42bfeffffffbfbbb0d857d546b73d541a7186236c10bd8fcad99ac8ac8cbc09f2f767c06fa9010000006a47304402203694bc6710583d656647be5efb814f1b2e953e769c47b76f3118100f89ca9f0802203a8b532ebea4ff4ab614a415ac77f230c8baaa2827bf01b8d58f1c87b3be6837012103146cf9fc4a1d278b61347709fecb373a1578c2de75217475ce1801ae3195cb28feffffff02f47d9303000000001976a914a744755a418c3275a94bb566a35f9a959abde33c88ac0065cd1d000000001976a91473f39e108cfe695a5d567b7fcbaf7180d5c4585f88ac0a95150002000000041ab2c513f05599d220029b5d7570116fa11bf3501a258c1cecfb3564e06d4fb7000000006b48304502210095518e4af84f4dd09bd97be24c0002a35a350a04548d12e440bbd7a73eef3f0702200a89caa33c7f1030dcbff44be20d132835e61778a4f76ce28005b36b01ee9b640121039742534e83193a3fd9b35f76d23dff4b861371fc65b795eb9500d3c7e53efc91feffffff2614a0fab2d46c56cec0c432e5135656f2e935b717e7dfc7eecfc683ea0213fb010000006b4830450221009490558c33a6986845f041e7fd7d773e039585730071a58e9c84989cb0f50371022020e04727080c5cfd17c1d72d99bc89e25a9c65ea6dcc10bf949e4a3375c8500701210261fcbbd0734e195bee2fd97bc3309be0c45033bcf89267e3cf234bc51eb27fd6feffffffbfbbb0d857d546b73d541a7186236c10bd8fcad99ac8ac8cbc09f2f767c06fa9000000006a47304402205269e3ab48ab97ade4e63ccb8689d8c0f2cdfcbdbecf75421be215353a60c48e02203daafbf11f9e56624501e77ca293403502382d006691dc0278bd8ed82c1cd47001210370dfd156c0825ddbf378c92737d5b0508a5d79ec8c0596ce2b9a218a22af03c9fefffffff33d09e61d9a66e264270c1e4a7f0c1cbece82819d1e2eeceb95a28c473b66a4010000006b483045022100a2345a23f79f1464f71c2233e889e4a2741851df9889dbcaae94bbd0fe022a9802206f5ff744b85f32561ca9c2c289c18226c4e5cae1061b02e2acaf917cacce259c012103794dfa80ae85ed3b6858e5d29ca2510c53becbabf39134fee85ebee094512876feffffff0200c2eb0b000000001976a91427a52653f243f0d13ec09d75c45d5dcedc00256188ac6ae41100000000001976a914cb81ed88074ec32d645b109bf12d0c6e67697fb388ac0a95150001000000019aeac9d0d1cf93ba8c94652295cc8e75cfa08bbf480effb9577366f499ff43c2010000006b483045022100cc0fc89897c6953dfaf9c42711849a6cc873288f45a24914e1089f73fdeea4870220747ca59d840d47763c195f2a9a7fbee745d1264d53bdcbb5d60cc91b2d8e9e27012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac7c571b2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000011af7c4b23a0b794bf1735810427078b299281cc09264fc3dd8ed8a1eb66754ab010000006b483045022100f5d34e355449681e811a97f0c95a676242629ca3622973419e8cf73e7ebcec500220022a97ae0f57b0793e0eb30a961664d469d3d1e1aafb0dc1cb515ded99a25ff9012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acf4cc192f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000150fb961c935295a4e3f01f816b64cd7760ba51fede9082a994e23b00cb5f8df1010000006a47304402203e2a3bbc2dc9f8a31c8c291255d30a46bb65e2a3435ffb17dccb7c54bc0bc61e0220767ac3665c88193a632049937752ed45f3191a12c93fed0177f8256bc023b575012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac6c42182f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000014d61abe0932a9ecdd55e1ec3c53a6cd39d6e90897e7811848b33cca216bfd3e4010000006b483045022100fb308ad5730105b1eb1b2030a3e7b2d2b77afdeac3eed27a62afb28a537795ec02207293392dea3562ba9f8812e001f7710e96b43a35d379761b1e7be062d8a2fa75012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ace4b7162f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001faeae981457dd1890a70d98cff06450d598fca86cec447959f217196fa27995d010000006a473044022067f4b7f3a8e92554f80ff4aef67402876be07be44c59cbb72fb0170c289697d602206e754b311723eee6e75a608366a44c2d02cf27552cb390553333cd48abea90d2012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac5c2d152f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000183a3f666d7d12e7f4ac4a47f362e82c0656b6b0386941edc7c3f2d22e65b3b56010000006b483045022100b27494a04fa59a44cab7f940dc9a73f99fd068283b3aedb1ff4d3ae95e38a1b602202b5794a9ebf546edb91127fb78f3ccd28a16346c14f7c47c4d44938c0b2d44a3012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acd4a2132f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001511fe35e4c40dad5548d3a08abdbcea5734c2b37147ea36728d65d6d8d278400010000006b483045022100bf8ee52e0394211e6a0699269dc7eca602689ff828424dddbb21a24969a9eb17022017b7180647fccc19645ede26bfd9d7ae6fb9717aa900e3d77ee41c56b5548adb012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac4c18122f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001414af7ac55f93aa412218cdc656be8f114be330c6acb96c7df1638ffcf542e68010000006a47304402203d3f88622398c2965d81bb151c84ee71772589fd614c3ad83185307f885d2491022051f0331ecb375011aa4dfd02b82ec666a7a5581a63b3bb3f2dfabd04f8b2e6d3012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acc48d102f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001296fd81b5983c64b70f6709845b841d3de5518ec9741b2516da27d88d08c256f010000006b483045022100d49bda7f021bae7f24661d5e5986fb02faf84c6aa983d2c4ffffdf62181a972502201cde297db33ff75b7ed21ff309c628dff7520871ed6e8b1435bf4aa45a8bbcde012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac3c030f2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001e218f62178d2a0ab0591f0aac96ace3bcab2b212df0a2bede5b1500522a644c3010000006a473044022036c117511e061a343ca0af948cbe92ba60326fac465014f18c9ff64fec45da05022061d8871717d6c7293d4d7bd4103223d9044b572aa0c1a9eec2cb52b1c6f908e9012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acb4780d2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000175cfe32945135f54ae8a177fe706106dc82171dcbfaa99b5ec869f5a1503782b010000006a473044022020c044ab4beb15f3b8a5bdea86ab8c84006ba20c71099dde544de88dd0dee93502200b8c9f9a782cf0314b70c58380718ca5487f0e037d5f1d92aa2a2da39e479563012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac2cee0b2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000018eabb5acc2c160a35e04d6c3bcd928f85e5963a8176f1871ec7bb04c887e72bd010000006a47304402200944bd03eaf0479ff3b1da8ec44f5386e630c44e4008997a355ddaf2e7521f990220253e0fc219da37ad7ae05384ee444ce4942e0907f0430d6f80c334086a34bbdf012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88aca4630a2f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001e41718acaa1a7a9dbcd55ebce7d38c6d72113f783a94919c59126be7ed6c8d96010000006a473044022034fae34b62cfe3b88a20cbb974e97ad2889d318d97ca6b16ae825b7f8c8cb677022069586e7540096565e10dddfe8b4459fb511aa0148c420337aa40ac398bd2cf68012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac1cd9082f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001723195292f326c9ab250494554c3290fe5e9cd3afab2516c5c5590ef055c6cb3010000006b483045022100f62940710d09303d61546bc6e4a934fb1d1bda2a0c61046b91c14eeb6420ba6e02206a9bf6e6f6c8a520523760ae3596e4c5d4a58c5c2e7d686300275c4f340abebc012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac944e072f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac00000000010000000102570071014c3ebc553d5a48e527c8efaa621a855a2cd333cd27e48caa7a1b41010000006b483045022100a9bd9327b80927ca3b473957d0c7a7cfb1c6c1913627b4d5841bd1aed4a5c01b022064d3765f9227e096ff164ffecab9a712d0f3d1de823d2fd15d5c50971caafcf7012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac0cc4052f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000018d177bdc2dfad1352c82ad1d46e5c8056559de1b526e14ddb55bc4c52c2f96d8010000006b483045022100887b9c8cb124ef91480918ed4785b685fee5d67b7d6d628c483e558b31af24bd022075721bd5d8d66e7cbcc837632c7053af4535f78a9515689d424b8a92fd9088aa012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac8439042f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001a38d9a9c8795852ee36632b9460166ff4c3165260fea757f2d9ed726a965fef2010000006b483045022100f3dfa79de58258eb29001eca6bb1c105b82d1fd9581bba7f88cda4f7aa35d3900220241ddca08f803cacd99757e3b9fe57d3aa7e197b1bc531b4115a6693459765ca012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acfcae022f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000012e70fd22a4b121731f14b95cfddd744aec88ae52d8e84f31eecb15c6c6bd26cf010000006a4730440220371aa478fd74f0e5de8e9162a4c1ba345dc141f83fee04f13c56b198bdb6984402205248da48da5634c4b2fbaf11fd24820e470e7c5ff776a86c1131406b15d7eb49012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac7424012f000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001a7a4ed4541fa614d44a53b8b4fd2999edc3e0f1fe27669b3e831dfdb4872310e010000006b4830450221009d53b960f14443912bd77b3ee2e65630694448bbd61b347831e47c9b39b0a92c02204915157a0c063a1f068ab912ff533f645480c4f656b6ad937818f63a52420a0e012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acec99ff2e000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001fd2910518fdea1b98ba913fa7293d9b8230211febc2c66957fd86d39b5bfdd61010000006a4730440220189b9c50e81b1ad885246bd8de3475b20d72e7c4625a5f4d3c194fe17a42776402206792d26e8b8d974bad632d40e2c531d1defa86c83857e6e27e96e9fcce2c7d7c012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac640ffe2e000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001d88f934b4077aade6681944eff6b70eda2e09f05053e8c01ee078b799897f468010000006b483045022100afd48f11265b896d7fe7fcf3b19f6ca853874c3f646873082e2bced31f6ff02c0220115216e6d48d6116a065046c1401ce0226cc7e4a05313a617b917bc99c5a0891012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acdc84fc2e000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000012969e5225a78e4b73f384c22d29a0043e96ed43cad42099b7139919f0e63784f010000006b483045022100c516265fe7efd4ffa0fa19241cf98d30b184cb0c76cdee6f2de8a6db76d55b4b02200acdaf136a5cc52e82d32e46f9b42939a058f55c2c0f40fe1b62732eb74d0d14012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac54fafa2e000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000016c8d2165b567ecf10bc3718dad0222520dff8c15cfdb8c432ca3dc4953e0d758010000006b483045022100aab5dd67f48d71800077a952fc36999511527c5d5e9a1e99d90887cc46d2ae58022035cb4d2b6f9e78454a46a9ada3783491b8eb2c4dc41c858e8be48f68a9867cb2012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88accc6ff92e000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001a4a38ccd5ee367e2923811299d941688f210a482d329e14f1857e995126c8a01010000006b483045022100c5f08321edd723a900d41b1383c44ad79c953583a19ba49c7f82a992653a8a12022034cb70db29cca85ccc4d866dc581089919ef019d1e7f4719795deb84e9623047012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88ac44e5f72e000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac000000000100000001bc045e013fbcb2df7375fadb1dc8f9c9d14880820114fabdfda2be8ff18627f9010000006a473044022069f36784c25c9e5a7a1ed93d8cd2a0f7d04057f132a555c8a232e5c652018275022055a338d8d1728c22c811eadd09344bf5c041ca73b3aed529892d67ea7d56c08e012103cecd0110d376992dba6292f7ba136da3e5576695a0d35f82eb82a46157c8d816ffffffff02a0860100000000001976a91467889225843555bab76f39f294b0130a2a6845de88acbc5af62e000000001976a914647809002cb87d1886e47a81a585d51df56b91a088ac0000000001000000000101d0f2390b69ff198ad7e98618af3ac0f25743d1865d2ad7fae0c313748d69f7d60000000023220020f9c03125ec1a2a1061f98471c110ca13e9038119a406ada42fba511417698d2affffffff0224cc3a000000000017a914de4eb874cc0a50b70fa52b6c5dac25a404df471487988401000000000017a9147a7002e4a4f99f7a40f1fc5b8d302a3ef4c8e960870400473044022021ad8ea0f7393ec20a821642035d51da2e76acb982597074ba0d2f6a4ae98522022004e5e2d11ad4c2a68ad794cfe700557164dbb23e98aaccb4d7d8977093bb551601483045022100e82c188c3c35e6c06b44bdfcb9637bac5a1b04c7ecbbba838c41f7b37b77222d02201ef57920dd48e1af40b7657d82732ebbb0ccc1c29ac9b9f3e4a952c23074ac4c01475221039085d4e779299dd6cd1384048f479168c0deb1fd76b7f85c192b170cdd0520042103c1a2c061ccc43ed429c690845342c39bfdf12412d6e8146b6bc3924a7e3008f452ae0000000002000000000101efcfcfe28c54efaf1e4bea776507e868e52e18a4f69411101453cb0eb41f6f830000000000a3b0288001019f0700000000002200203d6b7e30498d1db05774a1ecec79e4a93d8538fc05dc3564da10d671ef115e31040048304502210098d50741fce3591534455f87538b84c6c606e76ec9c6e883e12ffedd68df110902207e3dd2d7e5ea97ccebc3fba50e8ff9ade3e30425c6a4522b9aa317d68464c965014730440220362eb5ceda5c6127a5f71ed981eaed44ba7a36601b5103dd9fa1dafec3dc5eef02207350423b51b322ca377a4dbfa9296177736fecd0fca60c957a672573af846575014752210298745a228a9adfa99b7cb07dd48b48fce17dcd8ceff828f40583b994a78de9602103c431be3cbf4421e6395c814b9d4af0a504425a857d74540489adbe324543a24752ae59253920010000000219a6a19c5f337d8df602beee1cb9a7bef84d8dbb42418f9e9cbdb3d934ddaf81000000006b483045022100837202712341b7c14c3223fff64ccd8e1231f963cead65ebfc7fe2d78a28a648022074c86396df84e574b27ea5c2f280adc505812e7c925affe79fce4676784614f7012103ad6bcfd089907d8abdb6492d7817c3946235b55aa8bff1af27e49f038cc647abffffffffbb4dc5a37add3409fe85a452f4d5a31e00736a63d1b073f52ce685273a9f6364000000006b483045022100effd289060e054628d95e0768c0c91ad12542c0ff0aaf4109bbcd8c0790e819b02201bed14f244404065c15c0d7025c34969bcc31d65bad1e4bdfc3db7cf70863f550121025e83a6ec3be07fecbc656201d11d52f11cae3f6a0ca8f1294e87e53e40124245ffffffff02809698000000000017a914bfd1dcfd170e0c4f8e600d98421b2e042a2333f8871c490900000000001976a91409c313596b03cdd5925895d859d5d75bafa8e6c788ac00000000010000000001038881f68c6ce6a41ae11098f36b819c79b87f011500afa1dac504dae1645183060000000000feffffff1cea802ad1af4ead9b31eed70e8e6b3e242b1f2f0eb7663b7c5ab554126c05e50000000000feffffff0e07cfdd80dffbc0b2bacfc0e61081616622ab9c1831dc8ada843611a75b53ee0000000000feffffff02d1780000000000001600141cb6c9760dc6bff92379e31e8a806389d05c1926400d0300000000002200200305cdd34948ed8a402986b2ca5bbef74aa9bdf8f78a4e78b1dc12096db9e75a0247304402203b5f774c50eee3ab6a3359d4b0d1ac63e400f962f3d59dd4fa70f6eff11a402c02200b75a0f4c6b10dbee06a1ad7fa5ed73c49f332bba725d52bc4cb6a8bdc5e1a360121032fb9b90e001e43c319b527f2bc45eb4367f3cffc05e1f126abf994f8fcb5317802483045022100d42fe301c93fa8313a8db3348790ea0d9bc26a9d3eb9cac170c83d25c22d4a1702205de7ee6418871b41a2d3274fde2f6307e8dae001ac17bd8f761dc98bbe94ef09012103186098c6d1fe1ec9d5cbe0a89a826149b9bd91f39bbfa0c904b46ecb010b4da702483045022100f35f7495869a4a732e8935e5cb43cdc1d3f5f557691797ced3b4832fcc475f7102206d82ef6352293261eccfed82959926792326aee09967e44759a2a1007ad5c5f20121032fb9b90e001e43c319b527f2bc45eb4367f3cffc05e1f126abf994f8fcb531780a95150002000000000101b2496526ea5a950deefaf800f0738b753c14e13ec44a0282e2c4c7924360def501000000171600146ca14afcc0308946840ee127f5186e17250d5a9dffffffff02a0860100000000002200204821a654eb61c43948805f9c6f9a276b7be163dd0581df1fef32c44b12c23a07f5e04b000000000017a914160afa882a261db5f7dd23cae9c2c7ed45e19b65870247304402204692160e150d9f0cf5349ecd9f2ec8fa89449c32cbfed026379410ea8123c65602202920e196480868e8d23cc1a2b3e8cef0323a46c511a260b40d682d18950e559d012102a51dd6a055840ac5e769e8524de52c0f6c39d3164664b1727aa8de25a22a2b480000000002000000000101d614c5902e99dd45f5be52ef5efbe63c360e81a6b8b3af4cb99dc5152c443f5d0100000017160014f1a0641d179b464d00ad9c1cd793541853f15a9affffffff02a086010000000000220020a904398258815f41621d9197a250f762d5478c6cb0edc4509731f0d68e37cdb945584a000000000017a9141ddf4658d8aac1c3f564dbcbf24f2e8ec6a2eae38702473044022063a60ea751abc574da5799fa7c3d64e9d12cdd1203e6492b247caab6840437760220395d9a7f8d361d645a93116c5c221cf0523602063be0e4cc422a4ec562920653012102b5dd700ac29dc53b3045459b260bdfee11d07f5cd45025f9e65c89e1e637c66b00000000020000000001015f463c3defb23486cabc6ec0a0ecc1caf10ee681bf86251e90144077a53693ae0100000017160014007e5ea9e7f473d61f431a5a7fec80ed1af66025ffffffff02a086010000000000220020eb7c59bc984c4a9b7a224c7a94d332de9e07d83de77b7eb9ad0e3f02456eb6ed95cf48000000000017a914f1e1de854307a787e15d7c52ecbe2f5bf1f498e98702473044022064fac738b2d554d3c662029809c479dfb11174f6e34a19d20485582fde6a680d02204c7e2d97350cff8f14082d183cc845f54a58be2c680c420fcd5dcf9d8627fe6b012103f769c4cb2a01741b739b113ae415273a151b29b6c040f6b98d1e8775e415b9b20000000002000000000101a5cdd7023b8db47f32ca829246baf52d35282f423b336d5a9b120e0bfb619bd60100000017160014233a81053eac6ae607dba2bc2b7e1f342bd1653bffffffff02a086010000000000220020a786e7759ca36641f30a6410f6ed41eb37def3ccc4f8f35cd711a8b5a4714afbe54647000000000017a914177a3a66e985d01d444852167103b52c20872ec88702473044022032f4df6d4edd3de2cac8b3586469442a7e4e090c52da9b14f1659743aa8e113a02203a248b2b94412603e46db7963a5a08036463ec558608b7ea489327b27becc12b0121039b494c2bc82d8e1273b52a7d7044f2f7eb142958c36f62ca12eefb2f7e784ac7000000000200000000010152153ea1aeac155180b2ffe7efb33bfc7c1b36d8fb24c986ecd95fa3e6a0007f010000001716001414ddb678b7daffab111386122cd38232627a5370ffffffff02a086010000000000220020e4199af6da2befea03a19d55357037c8a3677a3efe8c52136c10244b3ab573de35be45000000000017a91470438c8a36b9ae8c9e0b92d0a3333f60cc7dbdbf8702483045022100bd5527752edfb925dead637f2e5b7f89119ad21589f451626e1753a89e5659b7022070b6992b86c575e5fcd39eeab71e683f2ec0c6da3595360dbfdf0ab3d8817ca3012102d061d6b0f08c518d00c0c78217d3bb71bd7e5cc6c655e44918bb75bfeeec5b8800000000020000000001022e4ba2522b550196cc0f35c76de858fcb07d7db9f1178b08a22a55ae059733510100000017160014a698e960e3c46bec460bb27d2fbe2b19b8c834c3ffffffff427f72ab4d76da7a213c17d84f070f04af15dd8b10c788e6d79a2319924ecf0e01000000171600148b1f6098fde1df7c8deff30347e0f20eee35af52ffffffff02808d5b0000000000220020ee74a112cceefa3cf27829e77cb0b0dcc86279cb2a6e898d0b8806b2812f3db164373d000000000017a91495cc3aa7bb0477b915fe9a226c6af1e2e41c71928702473044022067e87ab31836c62c9a0ca6ff1f01524f5568ea9dac425ad8bd14faae59a4720302203660357d9a392ad8563491678834862ff7e82a10bbf4d6f25849e5f705d0b22d0121028bfccf90e020a4747c3c7ee7b767828933832df750b84806e7d04560f08951ff024830450221009332461526c062140654cf6cc4298ea0251bb49fb850b5c92cbff40e81e3a33a02203c658d0dbec301f827eae34c522cc21f0ee966e67081ca9b00ec219796577d6401210337084a83f1ec75f212b40aa3c7382714b5c33ed5b2268540649ac05a3d2fa52c000000000200000000010119541f8a1640eaafa0e48971ff793b8f82df63ba226d479fd33dd49907d506f20000000000ffffffff014a410f000000000017a9141a55964ea738a53d3c394163eb3b4617b00175be870400473044022070f29d8edbececa612399896cf2cc2f54caa5f57aaf0e6ad49ce9a4255f8cff902203dbe77d27702009ebe20247e80a21170b3186c281d5ec8dce0abc5d3c34b601d014730440220262b70a4c0a643dd6e5ad333cbf07ff3646f94758a18c27563fc1a4882dcb62602200db2678bc8a64209503132cb803a84d2bd15615fc01fdf98641c63e05e6350c60147522102d7a6432c78fff83d02408d7066403122715d4e6d751a6202e24f036a98f8c7842103b38ad8c0413030369ba3066291e67ec6efa1474548391f0954242eb19a2ea02c52ae000000000100000001d84e76b707b2f7d360e9f06cdf6b0585445c8a4129350659ebc7c7d514f269f1010000006a473044022053b97f86746b0c77b7c7ff1e1a988febce8c2dc6e3e13f1b7e7ac6a1e5de63a0022058049dcf4dc9dae4b34197bb5eb811dae813615ad3eb95bade1e5203bb75e5e30121022920adca8d513dd06b521d0044b4e5c24f7526a3374c9f6464cc483e2bbc9ca9ffffffff0230430000000000001976a91475531af393ccbbfeef997cd21410fd4e694f5ca988ac3c630f00000000001976a914989d49db23a09fddf09251ebca96e81723616b4688ac000000000100000001675798ffaea8d9916e25e33fae9e5e26257bb11ea4ab30324cd3d3f42ea6e47a000000006b483045022100b6dbafec77186ab0e5f9bc77b2bd487fa95613fc50c9b8bcfd45a438dd00eb4e022056f9a9c3ec8423c19d924ccff4e3aae05d39cb671df9d1098662f750273f3ce0012103a676eabe3db01bc01d4bbecd7b0aeac982a6425c7357d395aca57e33c0938079ffffffff0260340300000000001976a9143de44997447d84b43fbec696b7991fbdc214867088ac307500000000000017a914cff056ee7a291336e178a1e9c171ad06d8d1e94b870000000002000000000101fab7268ecc1f002c6e2d2b3160f6524bde6fdea9803367974ed070307fbb7e5400000000000000000001b90600000000000022002065c26bf70bdb17e317b9821cf6944d9a58a8a3b6c6fa89b347b9c89ef2f2f5160500483045022100fcc59e56bd153264e9b789d744bd055c5ae2dae8d05034be6bc18f02befb3caa02207f437794bdfa7c97f365bcd204d45d702aedec1243508836a72041fe6a0e025a01483045022100e6b59b4f6bbaac04ddb8d549f50269301dc54feda421ce42087066d4bedb7ee50220325b8a62473408fb74ac7aefc51061f4babaf458ec39fc9e88f2fbcc4b1c1e1601008576a9140ab87314cfa61052a8cf5de14b71a98703ee0a278763ac6721037df049a96a11e19fe827893f37546d03c816627a7b74018d0598abc21fd0931e7c820120876475527c2102fd3c8d20d625844781c6a5eb8ac6402fa049d04ca42349b84f01766b2acb9f6652ae67a9140072836faa638d0a1146e1e4b45e089dba25d97f88ac6868099515000200000000010128261fbbb56dbaecbba1d791b1b00e27f4813682e3806092f133864eae86aabb0000000000e00100000102811e00000000001600141550da7e9fe679f2b8f929a0f152a8ed286ae9230347304402201bdd4e0cbd3d6f885d2fff9714b9d9d6b352766ef3cf2ddba674b24050aff6a2022033aa6c0f3e019289e8db56f43f2b5d1d3d93a6694e99de87762d937e635f569401004d632102c234db9f7c903786867a2e92f20575a347a30f00d37ae131905c117eada01cf26702e001b2752103be24487a89c0a7072e27a0927aed5b27608bb16b8538759354cd505cecde969768ac0000000001000000000101f50e673cac0cd5e3b1bd434d38061bdb616e0aa09752c61a64657ed3d6b656f40100000000ffffffff0218053d0000000000220020ed287ddc83215a0443835557ee68155aa4f4329a32487a0d0a658af5c4345a58e72f4d0100000000160014c2a06e48e5256d49357eb2bde8df00ae2b456d910247304402207658b1665123842ea15cbda438aec1d580e477a41d7a0ec16305d550830876eb02200297c7e4eb37f6d9312760f4555493cb91599ba1a23af60240005cc720d3634a01210243221c6e5362cf1e3ece104d3395d06b34d7ca0c2aae8515652d31f9e478756100000000010000000001010158309381bf477daaa603f08a7f8dcd012c9efade6cda992bfb061e9328358f0100000023220020c91accfdc338444a050f5037b87a59bc0bbd4a0dd70c941c0054322bee89ee0afdffffff02404b4c00000000001976a9148afaa5adcca38a8bffe532de7baf6117d44a064988ace3235b000000000017a91440d54d6164867ad00bf96453f537a46c2e6df8fa870400473044022062f88ccaec99333cf0e8d7b65e57ba681b191d6c5546f93c02b5bb1cee67c50502200d4c96d0cb7648201f31e4ca99c05aff3b5f035ff54e88fcf4395635d4b9353701483045022100d70ed1040e36c38846b5a0d2e8158262c040e51e5fe5849a6495fde3669fb82602200d8670184f62af3c669d8b0929e6072b51ba0845d90650d5527694b67e5d2a430147522102cf65ee04e3abc116c6513c6c1f3846fca181fb879a57f1597b837140b02c9508210369d3489c557eacad4fd21445451aedbe91fa8389d64850c5cce89426899fc0a052ae08951500010000000001019e16aa0f944d26048c7356459b8dcf038bae6c1b7714402b5e6514451b635e310000000023220020ac238325cf73887809f741de55ccb5d7538d93d41962d14fabd8c122856acda9fdffffff0210270000000000001976a9143a3dbfce170b64ed6611375bccff30c67a9f33f688ac5ed951280100000017a914905083ced8e9f91ffdefce331f918e77e24ce38b87040047304402207ab8f6ab4aa5dfcd4abd380117ebff0e43ad978c18ddf073873feaa2e7e93bb002207a1d04dba0e4f34d995e97d4c8b654e8755dbacbccb1695e81324e3842c3e06d01483045022100bab28e6f5e5633ecf4ff37408dcfc29cc6b309f8b29a292716f141d481ddc7ef022010db86445c8779bc53b90a05860aaa447d4eeef1c2d22e0bad276a9b9bb680ab0147522102d37c7f84c1acdd07d7924f9653b0cf2d3a43bebedebd0e897effdd2c74daecf32102e6e1973409fc9d71ffcc0df244679932e5b7b10c9bc9d9ac87af9f1c37f5dfeb52ae0a95150002000000000101e39ff8706ce19ae24cce30ae31d608e2eb7240c55ab42942d10d4b598a4396f60100000017160014f3e2ce182dca6a9a3df2a6f59dd50b7fa186e88ffeffffff0287def0000000000017a914f0e37fed73be40f4772825b058c10970da5e685887bc020000000000001976a9149f9a7abd600c0caa03983a77c8c3df8e062cb2fa88ac02483045022100c62844270c3cbb911059e29161fa6d195c815d675813d7a9a564665a4dc077230220294007e546ad9b1ec3194def88d425fc9d5ab030c4876e7b75a8332e651d2ede012103af6b99c2791cf6a854cea109b3873e9f722a7fd0e7b423b6da415574a619c25409951500020000000001019956ff6df2ecb75855160e403e28fba3a6b330942e738d96b7a76ed1df2e3c450000000017160014bc396fd6d56bb848c1850c064686e0cc14fc387ffeffffff02bc020000000000001976a9149f9a7abd600c0caa03983a77c8c3df8e062cb2fa88ac23dbf0000000000017a9148ab07d6457c9166c83e95dd2e618539c25d0f41d870247304402205d1e471424e89cecb1a6df524a95c6fea5865c450ed1bc89469c6f370a528413022065c01dc6b9c06667d8614dcf85dd15483157d7a2bd8f861c4bf76c3b2d1284240121029556788e9ca4bc83195f4109e96888b51adca7dcdeca9f2bf49fea4920ca11d30a951500 \ No newline at end of file diff --git a/src/tracker.rs b/src/tracker.rs new file mode 100644 index 0000000..c00f978 --- /dev/null +++ b/src/tracker.rs @@ -0,0 +1,102 @@ +use anyhow::{Context, Result}; +use bitcoin::{BlockHash, Txid}; +use serde_json::Value; + +use std::convert::TryInto; +use std::path::Path; + +use crate::{ + cache::Cache, + chain::Chain, + config::Config, + daemon::Daemon, + db::DBStore, + index::Index, + mempool::{Histogram, Mempool}, + metrics::Metrics, + status::Status, +}; + +/// Electrum protocol subscriptions' tracker +pub struct Tracker { + index: Index, + mempool: Mempool, + metrics: Metrics, + index_batch_size: usize, +} + +impl Tracker { + pub fn new(config: &Config) -> Result { + let metrics = Metrics::new(config.monitoring_addr)?; + let store = DBStore::open(Path::new(&config.db_path))?; + let chain = Chain::new(config.network); + Ok(Self { + index: Index::load(store, chain, &metrics).context("failed to open index")?, + mempool: Mempool::new(), + metrics, + index_batch_size: config.index_batch_size, + }) + } + + pub(crate) fn chain(&self) -> &Chain { + self.index.chain() + } + + pub(crate) fn fees_histogram(&self) -> &Histogram { + &self.mempool.fees_histogram() + } + + pub(crate) fn metrics(&self) -> &Metrics { + &self.metrics + } + + pub fn get_history(&self, status: &Status) -> impl Iterator { + let confirmed = status + .get_confirmed(&self.index.chain()) + .into_iter() + .map(|entry| entry.value()); + let mempool = status + .get_mempool(&self.mempool) + .into_iter() + .map(|entry| entry.value()); + confirmed.chain(mempool) + } + + pub fn sync(&mut self, daemon: &Daemon) -> Result<()> { + self.index.sync(daemon, self.index_batch_size)?; + self.mempool.sync(daemon)?; + // TODO: double check tip - and retry on diff + Ok(()) + } + + pub fn update_status( + &self, + status: &mut Status, + daemon: &Daemon, + cache: &Cache, + ) -> Result { + let prev_statushash = status.statushash(); + status.sync(&self.index, &self.mempool, daemon, cache)?; + Ok(prev_statushash != status.statushash()) + } + + pub fn get_balance(&self, status: &Status, cache: &Cache) -> bitcoin::Amount { + let unspent = status.get_unspent(&self.index.chain()); + let mut balance = bitcoin::Amount::ZERO; + for outpoint in &unspent { + let value = cache + .get_tx(&outpoint.txid, |tx| { + let vout: usize = outpoint.vout.try_into().unwrap(); + bitcoin::Amount::from_sat(tx.output[vout].value) + }) + .expect("missing tx"); + balance += value; + } + balance + } + + pub fn get_blockhash_by_txid(&self, txid: Txid) -> Option { + // Note: there are two blocks with coinbase transactions having same txid (see BIP-30) + self.index.filter_by_txid(txid).next() + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..26c6cc4 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,303 @@ +use anyhow::Result; + +use std::convert::TryInto; + +use bitcoin::{ + consensus::encode::{deserialize, serialize, Decodable, Encodable}, + hashes::{borrow_slice_impl, hash_newtype, hex_fmt_impl, index_impl, serde_impl, sha256, Hash}, + BlockHeader, OutPoint, Script, Txid, +}; + +use crate::db; + +macro_rules! impl_consensus_encoding { + ($thing:ident, $($field:ident),+) => ( + impl Encodable for $thing { + #[inline] + fn consensus_encode( + &self, + mut s: S, + ) -> Result { + let mut len = 0; + $(len += self.$field.consensus_encode(&mut s)?;)+ + Ok(len) + } + } + + impl Decodable for $thing { + #[inline] + fn consensus_decode( + mut d: D, + ) -> Result<$thing, bitcoin::consensus::encode::Error> { + Ok($thing { + $($field: Decodable::consensus_decode(&mut d)?),+ + }) + } + } + ); +} + +hash_newtype!( + ScriptHash, + sha256::Hash, + 32, + doc = "https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html#script-hashes", + true +); + +impl ScriptHash { + pub fn new(script: &Script) -> Self { + ScriptHash::hash(&script[..]) + } + + fn prefix(&self) -> ScriptHashPrefix { + let mut prefix = [0u8; PREFIX_LEN]; + prefix.copy_from_slice(&self.0[..PREFIX_LEN]); + ScriptHashPrefix { prefix } + } +} + +const PREFIX_LEN: usize = 8; + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct ScriptHashPrefix { + prefix: [u8; PREFIX_LEN], +} + +impl_consensus_encoding!(ScriptHashPrefix, prefix); + +type Height = u32; + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub(crate) struct ScriptHashRow { + prefix: ScriptHashPrefix, + height: Height, // transaction confirmed height +} + +impl_consensus_encoding!(ScriptHashRow, prefix, height); + +impl ScriptHashRow { + pub(crate) fn scan_prefix(scripthash: ScriptHash) -> Box<[u8]> { + scripthash.0[..PREFIX_LEN].to_vec().into_boxed_slice() + } + + pub(crate) fn new(scripthash: ScriptHash, height: usize) -> Self { + Self { + prefix: scripthash.prefix(), + height: height.try_into().expect("invalid height"), + } + } + + pub(crate) fn to_db_row(&self) -> db::Row { + serialize(self).into_boxed_slice() + } + + pub(crate) fn from_db_row(row: &[u8]) -> Self { + deserialize(&row).expect("bad ScriptHashRow") + } + + pub(crate) fn height(&self) -> usize { + self.height.try_into().expect("invalid height") + } +} + +// *************************************************************************** + +hash_newtype!( + StatusHash, + sha256::Hash, + 32, + doc = "https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html#status", + false +); + +// *************************************************************************** + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct SpendingPrefix { + prefix: [u8; PREFIX_LEN], +} + +impl_consensus_encoding!(SpendingPrefix, prefix); + +fn spending_prefix(prev: OutPoint) -> SpendingPrefix { + let txid_prefix = &prev.txid[..PREFIX_LEN]; + let value = u64::from_be_bytes(txid_prefix.try_into().unwrap()); + let value = value.wrapping_add(prev.vout.into()); + SpendingPrefix { + prefix: value.to_be_bytes(), + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub(crate) struct SpendingPrefixRow { + prefix: SpendingPrefix, + height: Height, // transaction confirmed height +} + +impl_consensus_encoding!(SpendingPrefixRow, prefix, height); + +impl SpendingPrefixRow { + pub(crate) fn scan_prefix(outpoint: OutPoint) -> Box<[u8]> { + Box::new(spending_prefix(outpoint).prefix) + } + + pub(crate) fn new(outpoint: OutPoint, height: usize) -> Self { + Self { + prefix: spending_prefix(outpoint), + height: height.try_into().expect("invalid height"), + } + } + + pub(crate) fn to_db_row(&self) -> db::Row { + serialize(self).into_boxed_slice() + } + + pub(crate) fn from_db_row(row: &[u8]) -> Self { + deserialize(&row).expect("bad SpendingPrefixRow") + } + + pub(crate) fn height(&self) -> usize { + self.height.try_into().expect("invalid height") + } +} + +// *************************************************************************** + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct TxidPrefix { + prefix: [u8; PREFIX_LEN], +} + +impl_consensus_encoding!(TxidPrefix, prefix); + +fn txid_prefix(txid: &Txid) -> TxidPrefix { + let mut prefix = [0u8; PREFIX_LEN]; + prefix.copy_from_slice(&txid[..PREFIX_LEN]); + TxidPrefix { prefix } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub(crate) struct TxidRow { + prefix: TxidPrefix, + height: Height, // transaction confirmed height +} + +impl_consensus_encoding!(TxidRow, prefix, height); + +impl TxidRow { + pub(crate) fn scan_prefix(txid: Txid) -> Box<[u8]> { + Box::new(txid_prefix(&txid).prefix) + } + + pub(crate) fn new(txid: Txid, height: usize) -> Self { + Self { + prefix: txid_prefix(&txid), + height: height.try_into().expect("invalid height"), + } + } + + pub(crate) fn to_db_row(&self) -> db::Row { + serialize(self).into_boxed_slice() + } + + pub(crate) fn from_db_row(row: &[u8]) -> Self { + deserialize(&row).expect("bad TxidRow") + } + + pub(crate) fn height(&self) -> usize { + self.height.try_into().expect("invalid height") + } +} + +// *************************************************************************** + +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct HeaderRow { + pub(crate) header: BlockHeader, +} + +impl_consensus_encoding!(HeaderRow, header); + +impl HeaderRow { + pub(crate) fn new(header: BlockHeader) -> Self { + Self { header } + } + + pub(crate) fn to_db_row(&self) -> db::Row { + serialize(self).into_boxed_slice() + } + + pub(crate) fn from_db_row(row: &[u8]) -> Self { + deserialize(&row).expect("bad HeaderRow") + } +} + +#[cfg(test)] +mod tests { + use crate::types::{spending_prefix, ScriptHash, ScriptHashRow, SpendingPrefix}; + use bitcoin::{hashes::hex::ToHex, Address, OutPoint, Txid}; + use serde_json::{from_str, json}; + + use std::str::FromStr; + + #[test] + fn test_scripthash_serde() { + let hex = "\"4b3d912c1523ece4615e91bf0d27381ca72169dbf6b1c2ffcc9f92381d4984a3\""; + let scripthash: ScriptHash = from_str(&hex).unwrap(); + assert_eq!(format!("\"{}\"", scripthash), hex); + assert_eq!(json!(scripthash).to_string(), hex); + } + + #[test] + fn test_scripthash_row() { + let hex = "\"4b3d912c1523ece4615e91bf0d27381ca72169dbf6b1c2ffcc9f92381d4984a3\""; + let scripthash: ScriptHash = from_str(&hex).unwrap(); + let row1 = ScriptHashRow::new(scripthash, 123456); + let db_row = row1.to_db_row(); + assert_eq!(db_row[..].to_hex(), "a384491d38929fcc40e20100"); + let row2 = ScriptHashRow::from_db_row(&db_row); + assert_eq!(row1, row2); + } + + #[test] + fn test_scripthash() { + let addr = Address::from_str("1KVNjD3AAnQ3gTMqoTKcWFeqSFujq9gTBT").unwrap(); + let scripthash = ScriptHash::new(&addr.script_pubkey()); + assert_eq!( + scripthash.to_hex(), + "00dfb264221d07712a144bda338e89237d1abd2db4086057573895ea2659766a" + ); + } + + #[test] + fn test_spending_prefix() { + let hex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; + let txid = Txid::from_str(hex).unwrap(); + + assert_eq!( + spending_prefix(OutPoint { txid, vout: 0 }), + SpendingPrefix { + prefix: [31, 30, 29, 28, 27, 26, 25, 24] + } + ); + assert_eq!( + spending_prefix(OutPoint { txid, vout: 10 }), + SpendingPrefix { + prefix: [31, 30, 29, 28, 27, 26, 25, 34] + } + ); + assert_eq!( + spending_prefix(OutPoint { txid, vout: 255 }), + SpendingPrefix { + prefix: [31, 30, 29, 28, 27, 26, 26, 23] + } + ); + assert_eq!( + spending_prefix(OutPoint { txid, vout: 256 }), + SpendingPrefix { + prefix: [31, 30, 29, 28, 27, 26, 26, 24] + } + ); + } +} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 129e65d..0000000 --- a/src/util.rs +++ /dev/null @@ -1,408 +0,0 @@ -use bitcoin::blockdata::block::BlockHeader; -use bitcoin::hash_types::BlockHash; -use std::collections::HashMap; -use std::convert::TryInto; -use std::fmt; -use std::iter::FromIterator; -use std::slice; -use std::sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender}; -use std::thread; - -pub type Bytes = Vec; -pub type HeaderMap = HashMap; - -// TODO: consolidate serialization/deserialize code for bincode/bitcoin. -const HASH_LEN: usize = 32; -pub const HASH_PREFIX_LEN: usize = 8; - -pub type FullHash = [u8; HASH_LEN]; -pub type HashPrefix = [u8; HASH_PREFIX_LEN]; - -pub fn hash_prefix(hash: &[u8]) -> HashPrefix { - hash[..HASH_PREFIX_LEN] - .try_into() - .expect("failed to convert into HashPrefix") -} - -pub fn full_hash(hash: &[u8]) -> FullHash { - hash.try_into().expect("failed to convert into FullHash") -} - -#[derive(Eq, PartialEq, Clone)] -pub struct HeaderEntry { - height: usize, - hash: BlockHash, - header: BlockHeader, -} - -impl HeaderEntry { - pub fn hash(&self) -> &BlockHash { - &self.hash - } - - pub fn header(&self) -> &BlockHeader { - &self.header - } - - pub fn height(&self) -> usize { - self.height - } -} - -impl fmt::Debug for HeaderEntry { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let spec = time::Timespec::new(i64::from(self.header().time), 0); - let last_block_time = time::at_utc(spec).rfc3339().to_string(); - write!( - f, - "best={} height={} @ {}", - self.hash(), - self.height(), - last_block_time, - ) - } -} - -struct HashedHeader { - blockhash: BlockHash, - header: BlockHeader, -} - -fn hash_headers(headers: Vec) -> Vec { - // header[i] -> header[i-1] (i.e. header.last() is the tip) - let hashed_headers = - Vec::::from_iter(headers.into_iter().map(|header| HashedHeader { - blockhash: header.block_hash(), - header, - })); - for i in 1..hashed_headers.len() { - assert_eq!( - hashed_headers[i].header.prev_blockhash, - hashed_headers[i - 1].blockhash - ); - } - hashed_headers -} - -pub struct HeaderList { - headers: Vec, - heights: HashMap, -} - -impl HeaderList { - pub fn empty() -> HeaderList { - HeaderList { - headers: vec![], - heights: HashMap::new(), - } - } - - pub fn order(&self, new_headers: Vec) -> Vec { - // header[i] -> header[i-1] (i.e. header.last() is the tip) - let hashed_headers = hash_headers(new_headers); - let prev_blockhash = match hashed_headers.first() { - Some(h) => h.header.prev_blockhash, - None => return vec![], // hashed_headers is empty - }; - let null_hash = BlockHash::default(); - let new_height: usize = if prev_blockhash == null_hash { - 0 - } else { - self.header_by_blockhash(&prev_blockhash) - .unwrap_or_else(|| panic!("{} is not part of the blockchain", prev_blockhash)) - .height() - + 1 - }; - (new_height..) - .zip(hashed_headers.into_iter()) - .map(|(height, hashed_header)| HeaderEntry { - height, - hash: hashed_header.blockhash, - header: hashed_header.header, - }) - .collect() - } - - pub fn apply(&mut self, new_headers: Vec, tip: BlockHash) { - if tip == BlockHash::default() { - assert!(new_headers.is_empty()); - self.heights.clear(); - self.headers.clear(); - return; - } - // new_headers[i] -> new_headers[i - 1] (i.e. new_headers.last() is the tip) - for i in 1..new_headers.len() { - assert_eq!(new_headers[i - 1].height() + 1, new_headers[i].height()); - assert_eq!( - *new_headers[i - 1].hash(), - new_headers[i].header().prev_blockhash - ); - } - let new_height = match new_headers.first() { - Some(entry) => { - // Make sure tip is consistent (if there are new headers) - let expected_tip = new_headers.last().unwrap().hash(); - assert_eq!(tip, *expected_tip); - // Make sure first header connects correctly to existing chain - let height = entry.height(); - let expected_prev_blockhash = if height > 0 { - *self.headers[height - 1].hash() - } else { - BlockHash::default() - }; - assert_eq!(entry.header().prev_blockhash, expected_prev_blockhash); - // First new header's height (may override existing headers) - height - } - // No new headers - chain's "tail" may be removed - None => { - let tip_height = *self - .heights - .get(&tip) - .unwrap_or_else(|| panic!("missing tip: {}", tip)); - tip_height + 1 // keep the tip, drop the rest - } - }; - debug!( - "applying {} new headers from height {}", - new_headers.len(), - new_height - ); - self.headers.truncate(new_height); // keep [0..new_height) entries - assert_eq!(new_height, self.headers.len()); - for new_header in new_headers { - assert_eq!(new_header.height(), self.headers.len()); - assert_eq!(new_header.header().prev_blockhash, self.tip()); - self.heights.insert(*new_header.hash(), new_header.height()); - self.headers.push(new_header); - } - assert_eq!(tip, self.tip()); - assert!(self.heights.contains_key(&tip)); - } - - pub fn header_by_blockhash(&self, blockhash: &BlockHash) -> Option<&HeaderEntry> { - let height = self.heights.get(blockhash)?; - let header = self.headers.get(*height)?; - if *blockhash == *header.hash() { - Some(header) - } else { - None - } - } - - pub fn header_by_height(&self, height: usize) -> Option<&HeaderEntry> { - self.headers.get(height).map(|entry| { - assert_eq!(entry.height(), height); - entry - }) - } - - pub fn equals(&self, other: &HeaderList) -> bool { - self.headers.last() == other.headers.last() - } - - pub fn tip(&self) -> BlockHash { - self.headers.last().map(|h| *h.hash()).unwrap_or_default() - } - - pub fn len(&self) -> usize { - self.headers.len() - } - - pub fn is_empty(&self) -> bool { - self.headers.is_empty() - } - - pub fn iter(&self) -> slice::Iter { - self.headers.iter() - } -} - -pub struct SyncChannel { - tx: SyncSender, - rx: Receiver, -} - -impl SyncChannel { - pub fn new(size: usize) -> SyncChannel { - let (tx, rx) = sync_channel(size); - SyncChannel { tx, rx } - } - - pub fn sender(&self) -> SyncSender { - self.tx.clone() - } - - pub fn receiver(&self) -> &Receiver { - &self.rx - } - - pub fn into_receiver(self) -> Receiver { - self.rx - } -} - -pub struct Channel { - tx: Sender, - rx: Receiver, -} - -impl Channel { - pub fn unbounded() -> Self { - let (tx, rx) = channel(); - Channel { tx, rx } - } - - pub fn sender(&self) -> Sender { - self.tx.clone() - } - - pub fn receiver(&self) -> &Receiver { - &self.rx - } - - pub fn into_receiver(self) -> Receiver { - self.rx - } -} - -pub fn spawn_thread(name: &str, f: F) -> thread::JoinHandle -where - F: FnOnce() -> T, - F: Send + 'static, - T: Send + 'static, -{ - thread::Builder::new() - .name(name.to_owned()) - .spawn(f) - .unwrap() -} - -#[cfg(test)] -mod tests { - #[test] - fn test_headers() { - use bitcoin::blockdata::block::BlockHeader; - use bitcoin::hash_types::{BlockHash, TxMerkleNode}; - use bitcoin::hashes::Hash; - - use super::HeaderList; - - // Test an empty header list - let null_hash = BlockHash::default(); - let mut header_list = HeaderList::empty(); - assert_eq!(header_list.tip(), null_hash); - let ordered = header_list.order(vec![]); - assert_eq!(ordered.len(), 0); - header_list.apply(vec![], null_hash); - - let merkle_root = TxMerkleNode::hash(&[255]); - let mut headers = vec![BlockHeader { - version: 1, - prev_blockhash: BlockHash::default(), - merkle_root, - time: 0, - bits: 0, - nonce: 0, - }]; - for _height in 1..10 { - let prev_blockhash = headers.last().unwrap().block_hash(); - let header = BlockHeader { - version: 1, - prev_blockhash, - merkle_root, - time: 0, - bits: 0, - nonce: 0, - }; - headers.push(header); - } - - // Test adding some new headers - let ordered = header_list.order(headers[..3].to_vec()); - assert_eq!(ordered.len(), 3); - header_list.apply(ordered.clone(), ordered[2].hash); - assert_eq!(header_list.len(), 3); - assert_eq!(header_list.tip(), ordered[2].hash); - for h in 0..3 { - let entry = header_list.header_by_height(h).unwrap(); - assert_eq!(entry.header, headers[h]); - assert_eq!(entry.hash, headers[h].block_hash()); - assert_eq!(entry.height, h); - assert_eq!(header_list.header_by_blockhash(&entry.hash), Some(entry)); - } - - // Test adding some more headers - let ordered = header_list.order(headers[3..6].to_vec()); - assert_eq!(ordered.len(), 3); - header_list.apply(ordered.clone(), ordered[2].hash); - assert_eq!(header_list.len(), 6); - assert_eq!(header_list.tip(), ordered[2].hash); - for h in 0..6 { - let entry = header_list.header_by_height(h).unwrap(); - assert_eq!(entry.header, headers[h]); - assert_eq!(entry.hash, headers[h].block_hash()); - assert_eq!(entry.height, h); - assert_eq!(header_list.header_by_blockhash(&entry.hash), Some(entry)); - } - - // Test adding some more headers (with an overlap) - let ordered = header_list.order(headers[5..].to_vec()); - assert_eq!(ordered.len(), 5); - header_list.apply(ordered.clone(), ordered[4].hash); - assert_eq!(header_list.len(), 10); - assert_eq!(header_list.tip(), ordered[4].hash); - for h in 0..10 { - let entry = header_list.header_by_height(h).unwrap(); - assert_eq!(entry.header, headers[h]); - assert_eq!(entry.hash, headers[h].block_hash()); - assert_eq!(entry.height, h); - assert_eq!(header_list.header_by_blockhash(&entry.hash), Some(entry)); - } - - // Reorg the chain and test apply() on it - for h in 8..10 { - headers[h].nonce += 1; - headers[h].prev_blockhash = headers[h - 1].block_hash() - } - // Test reorging the chain - let ordered = header_list.order(headers[8..10].to_vec()); - assert_eq!(ordered.len(), 2); - header_list.apply(ordered.clone(), ordered[1].hash); - assert_eq!(header_list.len(), 10); - assert_eq!(header_list.tip(), ordered[1].hash); - for h in 0..10 { - let entry = header_list.header_by_height(h).unwrap(); - assert_eq!(entry.header, headers[h]); - assert_eq!(entry.hash, headers[h].block_hash()); - assert_eq!(entry.height, h); - assert_eq!(header_list.header_by_blockhash(&entry.hash), Some(entry)); - } - - // Test "trimming" the chain - header_list.apply(vec![], headers[7].block_hash()); - assert_eq!(header_list.len(), 8); - assert_eq!(header_list.tip(), headers[7].block_hash()); - for h in 0..8 { - let entry = header_list.header_by_height(h).unwrap(); - assert_eq!(entry.header, headers[h]); - assert_eq!(entry.hash, headers[h].block_hash()); - assert_eq!(entry.height, h); - assert_eq!(header_list.header_by_blockhash(&entry.hash), Some(entry)); - } - - // Test "un-trimming" the chain - let ordered = header_list.order(headers[8..].to_vec()); - assert_eq!(ordered.len(), 2); - header_list.apply(ordered.clone(), ordered[1].hash); - assert_eq!(header_list.len(), 10); - assert_eq!(header_list.tip(), ordered[1].hash); - for h in 0..10 { - let entry = header_list.header_by_height(h).unwrap(); - assert_eq!(entry.header, headers[h]); - assert_eq!(entry.hash, headers[h].block_hash()); - assert_eq!(entry.height, h); - assert_eq!(header_list.header_by_blockhash(&entry.hash), Some(entry)); - } - } -} diff --git a/sync.sh b/sync.sh new file mode 100755 index 0000000..de14906 --- /dev/null +++ b/sync.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -eux +cd `dirname $0` + +cargo fmt --all +cargo build --all --release + +NETWORK=$1 +shift + +CMD="target/release/sync --network $NETWORK --db-dir ./db2 --daemon-dir $HOME/.bitcoin" +export RUST_LOG=${RUST_LOG-info} +$CMD $* + +# use SIGINT to quit diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 0000000..e3f807d --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,102 @@ +#!/bin/bash +set -euo pipefail + +rm -rf data/ +mkdir -p data/{bitcoin,electrum,electrs} + +cleanup() { + trap - SIGTERM SIGINT + set +eo pipefail + jobs + for j in `jobs -rp` + do + kill $j + wait $j + done +} +trap cleanup SIGINT SIGTERM EXIT + +BTC="bitcoin-cli -regtest -datadir=data/bitcoin" +ELECTRUM="electrum --regtest" +EL="$ELECTRUM --wallet=data/electrum/wallet" + +tail_log() { + tail -n +0 -F $1 || true +} + +echo "Starting $(bitcoind -version | head -n1)..." +bitcoind -regtest -datadir=data/bitcoin -printtoconsole=0 & +BITCOIND_PID=$! + +$BTC -rpcwait getblockcount > /dev/null + +echo "Creating Electrum `electrum version --offline` wallet..." +WALLET=`$EL --offline create --seed_type=segwit` +MINING_ADDR=`$EL --offline getunusedaddress` + +$BTC generatetoaddress 110 $MINING_ADDR > /dev/null +echo `$BTC getblockchaininfo | jq -r '"Generated \(.blocks) regtest blocks (\(.size_on_disk/1e3) kB)"'` to $MINING_ADDR + +TIP=`$BTC getbestblockhash` + +export RUST_LOG=electrs=debug +electrs --db-dir=data/electrs --daemon-dir=data/bitcoin --network=regtest 2> data/electrs/regtest-debug.log & +ELECTRS_PID=$! +tail_log data/electrs/regtest-debug.log | grep -m1 "serving Electrum RPC" + +$ELECTRUM daemon --server localhost:60401:t -1 -vDEBUG 2> data/electrum/regtest-debug.log & +ELECTRUM_PID=$! +tail_log data/electrum/regtest-debug.log | grep -m1 "connection established" +$EL getinfo | jq . + +echo "Loading Electrum wallet..." +test `$EL load_wallet` == "true" + +echo "Running integration tests:" + +echo " * getbalance" +test "`$EL getbalance | jq -c .`" == '{"confirmed":"550","unmatured":"4950"}' + +echo " * getunusedaddress" +NEW_ADDR=`$EL getunusedaddress` + +echo " * payto & broadcast" +TXID=$($EL broadcast $($EL payto $NEW_ADDR 123 --fee 0.001)) + +echo " * get_tx_status" +test "`$EL get_tx_status $TXID | jq -c .`" == '{"confirmations":0}' + +echo " * getaddresshistory" +test "`$EL getaddresshistory $NEW_ADDR | jq -c .`" == "[{\"fee\":100000,\"height\":0,\"tx_hash\":\"$TXID\"}]" + +echo " * getbalance" +test "`$EL getbalance | jq -c .`" == '{"confirmed":"550","unconfirmed":"-0.001","unmatured":"4950"}' + +echo "Generating bitcoin block..." +$BTC generatetoaddress 1 $MINING_ADDR > /dev/null +$BTC getblockcount > /dev/null + +echo " * wait for new block" +kill -USR1 $ELECTRS_PID # notify server to index new block +tail_log data/electrum/regtest-debug.log | grep -m1 "verified $TXID" > /dev/null + +echo " * get_tx_status" +test "`$EL get_tx_status $TXID | jq -c .`" == '{"confirmations":1}' + +echo " * getaddresshistory" +test "`$EL getaddresshistory $NEW_ADDR | jq -c .`" == "[{\"height\":111,\"tx_hash\":\"$TXID\"}]" + +echo " * getbalance" +test "`$EL getbalance | jq -c .`" == '{"confirmed":"599.999","unmatured":"4950.001"}' + +echo "Electrum `$EL stop`" # disconnect wallet +wait $ELECTRUM_PID + +kill -INT $ELECTRS_PID # close server +tail_log data/electrs/regtest-debug.log | grep -m1 "stopping Electrum RPC server" +wait $ELECTRS_PID + +$BTC stop # stop bitcoind +wait $BITCOIND_PID + +echo "=== PASSED ===" From d442501e2677d510f71015cbfc133814e8005aa2 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 16 Apr 2021 09:46:35 +0300 Subject: [PATCH 002/113] Fixup Rust version in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e3c590c..1a987f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ### Electrum Rust Server ### -FROM rust:1.48.0-slim as electrs-build +FROM rust:1.41.1-slim as electrs-build RUN apt-get update RUN apt-get install -qq -y clang cmake RUN rustup component add rustfmt From d0b00a84bcf7b7b8b8aa471fd4357b770488b6ae Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 23 Apr 2021 20:59:36 +0300 Subject: [PATCH 003/113] Test merkle proof generation using 2 mainnet blocks --- src/merkle.rs | 83 +++++++++++++----- ...a3d89f63ef3fee203adcca7c24008c13fd854513f2 | Bin 0 -> 998978 bytes ...ea455e38612bdf36e9967fdead11935c8e22283ecc | Bin 0 -> 99980 bytes 3 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 src/tests/blocks/000000000000000002d249a3d89f63ef3fee203adcca7c24008c13fd854513f2 create mode 100644 src/tests/blocks/00000000000000001203c1ea455e38612bdf36e9967fdead11935c8e22283ecc diff --git a/src/merkle.rs b/src/merkle.rs index 65086b9..d50f6a5 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -52,28 +52,63 @@ impl Proof { } } -// TODO: add tests +#[cfg(test)] +mod tests { + use bitcoin::{consensus::encode::deserialize, Block, Txid}; + use std::path::Path; -// // {"id":37,"jsonrpc":"2.0", -// "result":{"block_height":333961,"merkle":[ -// "5d8cfb001d9ec17861ad9c158244239cb6e3298a619b2a5f7b176ddd54459c75", -// "06811172e13312f2e496259d2c8a7262f1192be5223fcf4d6a9ed7f58a2175ba", -// "cbcec841dea3294706809d1510c72b4424d141fac89106af65b70399b1d79f3f", -// "a24d6c3601a54d40f4350e6c8887bf82a873fe8619f95c772b573ec0373119d3", -// "2015c1bb133ee2c972e55fdcd205a9aee7b0122fd74c2f5d5d27b24a562c7790", -// "f379496fef2e603c4e1c03e2179ebaf5153d6463b8d61aa16d41db3321a18165", -// "7a798d6529663fd472d26cc90c434b64f78955747ac2f93c8dcd35b8f684946e", -// "ad3811062b8db664f2342cbff1b491865310b74416dd7b901f14d980886821f8"],"pos":157}} -// // {"id":38,"jsonrpc":"2.0", -// "result":{"block_height":437303,"merkle":[ -// "d29769df672657689fd6d293b416ee9211c77fbe243ab7820813f327b0e8dd47", -// "d71f0947b47cab0f64948acfe52d41c293f492fe9627690c330d4004f2852ce4", -// "5f36c4330c727d7c8d98cc906cb286f13a61b5b4cab2124c5d041897834b42d8", -// "e77d181f83355ed38d0e6305fdb87c9637373fd90d1dfb911262ac55d260181e", -// "a8f83ca44dc486d9d45c4cff9567839c254bda96e6960d310a5e471c70c6a95b", -// "e9a5ff7f74cb060b451ed2cd27de038efff4df911f4e0f99e2661b46ebcc7e1c", -// "6b0144095e3f0e0d0551cbaa6c5dfc89387024f836528281b6d290e356e196cf", -// "bb0761b0636ffd387e0ce322289a3579e926b6813e090130a88228bd80cff982", -// "ac327124304cccf6739da308a25bb365a6b63e9344bad2be139b0b02c042567c", -// "42e11f2d67050cd31295f85507ebc7706fc4c1fddf1e5a45b98ae3f7c63d2592", -// "52657042fcfc88067524bf6c5f9a66414c7de4f4fcabcb65bca56fa84cf309b4"],"pos":6}} + use super::Proof; + + #[test] + fn test_merkle() { + let proof = Proof::create( + &load_block_txids("00000000000000001203c1ea455e38612bdf36e9967fdead11935c8e22283ecc"), + 157, + ); + assert_eq!( + proof.to_hex(), + vec![ + "5d8cfb001d9ec17861ad9c158244239cb6e3298a619b2a5f7b176ddd54459c75", + "06811172e13312f2e496259d2c8a7262f1192be5223fcf4d6a9ed7f58a2175ba", + "cbcec841dea3294706809d1510c72b4424d141fac89106af65b70399b1d79f3f", + "a24d6c3601a54d40f4350e6c8887bf82a873fe8619f95c772b573ec0373119d3", + "2015c1bb133ee2c972e55fdcd205a9aee7b0122fd74c2f5d5d27b24a562c7790", + "f379496fef2e603c4e1c03e2179ebaf5153d6463b8d61aa16d41db3321a18165", + "7a798d6529663fd472d26cc90c434b64f78955747ac2f93c8dcd35b8f684946e", + "ad3811062b8db664f2342cbff1b491865310b74416dd7b901f14d980886821f8" + ] + ); + + let proof = Proof::create( + &load_block_txids("000000000000000002d249a3d89f63ef3fee203adcca7c24008c13fd854513f2"), + 6, + ); + assert_eq!( + proof.to_hex(), + vec![ + "d29769df672657689fd6d293b416ee9211c77fbe243ab7820813f327b0e8dd47", + "d71f0947b47cab0f64948acfe52d41c293f492fe9627690c330d4004f2852ce4", + "5f36c4330c727d7c8d98cc906cb286f13a61b5b4cab2124c5d041897834b42d8", + "e77d181f83355ed38d0e6305fdb87c9637373fd90d1dfb911262ac55d260181e", + "a8f83ca44dc486d9d45c4cff9567839c254bda96e6960d310a5e471c70c6a95b", + "e9a5ff7f74cb060b451ed2cd27de038efff4df911f4e0f99e2661b46ebcc7e1c", + "6b0144095e3f0e0d0551cbaa6c5dfc89387024f836528281b6d290e356e196cf", + "bb0761b0636ffd387e0ce322289a3579e926b6813e090130a88228bd80cff982", + "ac327124304cccf6739da308a25bb365a6b63e9344bad2be139b0b02c042567c", + "42e11f2d67050cd31295f85507ebc7706fc4c1fddf1e5a45b98ae3f7c63d2592", + "52657042fcfc88067524bf6c5f9a66414c7de4f4fcabcb65bca56fa84cf309b4" + ] + ); + } + + fn load_block_txids(block_hash_hex: &str) -> Vec { + let path = Path::new("src") + .join("tests") + .join("blocks") + .join(block_hash_hex); + eprintln!("{:?}", path); + let data = std::fs::read(path).unwrap(); + let block: Block = deserialize(&data).unwrap(); + block.txdata.iter().map(|tx| tx.txid()).collect() + } +} diff --git a/src/tests/blocks/000000000000000002d249a3d89f63ef3fee203adcca7c24008c13fd854513f2 b/src/tests/blocks/000000000000000002d249a3d89f63ef3fee203adcca7c24008c13fd854513f2 new file mode 100644 index 0000000000000000000000000000000000000000..7f9598c71add610a5ee8e49717c8c59dae651b42 GIT binary patch literal 998978 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 z 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

LF`Bn8wC)8c2JR0sMJE|#~ce@M*FNe}7J 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%0Df3z1?rLEM8`Jyx~a z50OJ8yDb>Iq4`dgAJn?EhO+let^#a%u+p`OvBPIRb{g*SPvH1 z^cRCPT$^&Mb{0oiS*B<0i{!qprrPN&0#+11n(3!LNACyVAHZ8@@=5eQ>N}U#nu@jU z=C6nD+P4=g^ow5zV^af*cZm+4cKG;)-Y*C~<3v@KH#coNixNk#EQE;K{$qpq8w!lC zV%P8pvP!oPi%Yxu1kf6)w}Ez8E9{D9V&b z%JRqSpI?wv=*^Z%RP!&udQE^9p~$aVue)y%HSZifyp*>w z&T!2!4JM@QK|%h^=(aERo(%R!Oh=k!T=n)4+PxU0zNQlT?_SFskp&iO|-y{x{OxnN=#E>4F^tDzvhL zR5?Omo-qg6kL62^o8Q4WbEzA+REH)ifBtq20*kp*@aW^OlO#x%1>Pq0RS<7_%?APm z+Q0xV)%XGTW=qp80vB^iQZC1@DID`b_M>t=(xiSh`PK#*&1%9BPZec*6S1Z={F$1h zNtO}V>=-b>*Pb}_BUV4&RHvoXFh=_8IlGIaAXL-qNG>*{3MCBvC(I$@H+RmCc|C^8 z>-MIz)ZjG?b*FG;Z)z8v2*c=|mQ!)&-|eK6m*F=v@|2_}f8} z6Oo&I`WAdyKb~FB1|Sx+suz-)gYqAwuiI)atC#)QVpfKdo!IL0bylD2Tg#ug+<2U> zS=%J%yg6ISk0#aFojpOjd$3M_>EC$4Zj9KK48Uovvy!+y{@t*)xf~=c(UEd2x2-`! ziq`Ft>RP^~+5-|GIv1*RB&NPC!%;TzxG(Vkfv6oRaJ<1g`=J1op>#<7A&8lMKC8^WP4b|; zC3EtLA~$+bH>_vO%V3}8Zaj(4|JzHxi~;v2t;b2QfmV+Es(yL*m#AGYg5pB_vcW&h z)t%M51z+SlfK$ELhZfS4NN?G%hR^?iGlo=v6fx0`@npFDtu^m54fWq4(P#X|D|SCH zo8tf77g!gBJjw@GrVlH>+6&C2(mSO5CHK9;E{`vyrQ+m%&@5YIL&i0OLrvGIrTD5E z6-4K-H#Od)<#NB(=^`m#PswK1MDj0;x?Uu7;H?I2D@Fkeo!HbeiC$F486~fgSnm+8 z1PhlWgKt~#n0|28saK;{*aBhh85c*`rhQmXIgYZ(j^cXqn{wV*+i0E+Mt+-z5LOvj zWJmqR3d-5oGn#X&hQv2^oa@f>3;smwHdyS-4_y1UYZfjK=!)kjhqO0Y6mWzWj*jV(p@dP}?nVXn{rYdIUbwmHvk(bre-ZSDMJ9cFFn@c?>& z9GweP$nVE~@ZTy>ub~9f5*)|;HX;<9J8nhQJ1a}O<31$*bVhoP7D3@T*C>?*x@s?= z14sR7zvUr@nY&9j>9NWo9As54EF*z%`o@5~cB#>nvcunsFAjxIanNUSF0DXoj7ZGo zyb0|72e3baU-vNnBtXtc)WYPQlj=UUd!Sy(l+500tdiY1^E^!$U`q23a}O6sWs!?5 zDX9hF5i}+_B|~EYl)z#XvA>whV+DLFm(Z(XOvn_><*!fSegK32hk2S$&$z841QVFQ zh~h?hw9Kh{MT>|;8Hb@ZeQv&q)c?<{&7rY98viOggFo0jgjt(dt$&) z!*o89Rg6<*;XI z2@-(bLMF5I_UH|{qrn(o22S`$J@AbW6nt_h{K7ocur7H8l0IbF zcho=AITEGQpDUkco9(H_SjEg_V1l?{7Fx7zUVu-4m-*zAHT|e#@v@STD;34^*FIqv zdtiuK#4EPd{D>} zwudCN{0$dYTby#}sM|)s%Vl~SF1rMO#_Xy7nfR+%Ds=_@oCn1TQHNf_qGWybP!Q(z zC5`&7)d80)hVPXZaaFyiLR)fv;+m*;1ij>qFS-sPfp88~F3?zt#ciBN?+)EZCiQ_{ zLZUmVSj~rpSL}0)Z6g675t`eH$BHAl_1fv3(=-uuvhrGd^&_&Ndh2ZWS-&mp8S~$J zPxV)il7k)jquaSDmKs{tq|IaSfH7l@IDhR|{Q$YOGoEbg+rr_EQU zd?XM;2?hAs;)KR0)+R3k;8F-ZH5g%#=Z1w!q7=>#Y1M(_Qu~F!jw%!#gAIuqf7vQwFkCVR$w7EUee5ey*a3TH^Uvyj3rWE9^1Nv)k-nq23C0ghzX zPr~Ps&P`Qk@&b*2z^b-;H5sDn4JiKTc=;cgt4JSx%GJ*5dBGM+_cfn>a@->hri%NB za;Az;A!DO+?9ur7YqwYs<<>j6br{>oHfQ$c<_AZo8QV8C7E-pfe<=N#%~ACtrgB-ypk`Kn65Af}8FMx{khFfn z^ekNFy#lxFEnQSQdgvEL(vH_kxrJ0R+?(BP?_5Ut`Y$g-!uGmJ32S&DHH53<_5E*>f|CR`1{0l%QNJ0U z??g>03>th(x z{8u8k>ovtGYZ7kszVNT>o-haa2|D6ATxWqvI~y|1@kJPgs9FGO z$U>x_t1Mfkqas@@5HHMY1B-0Mq(WNjV`^$uew^v|F#y|ZO1>;#=@YC@m-gMBM{YTG z5LG#;Vz(VRQ9B;x%R`rc#$0dV$({6w=qJVvHZumqI_r9O&6{D}W@8MO+W_y5-bXfg zk}Vo_UE^;@mRUVdAYi$J3@5(~VJ7_toqJ1i4B<-8I1aG3~7JgwPyVqupoz5PUzG z*q^;53Omg%?P6|*r9I=>=*A^4eVcDE`iyxc=HwSz|C_)9;+WREi$Nx*D$*#o#s*mg zWkF}CZ82aJLwZKKNf~zi#Y4xQUhznS=p#N)eU;EQ(hagqgZ8>t~-iW*S|w-TSTf>)Uc!RT`ZM zu7?hC#5Hh|6@Ac&N+iB6CY$0bYU9?_H!~jW{tR;wo$NlPh}ar`;zk*=Q;%3YVLGxt z2U8X&;};abXj2a^0Sye+xC9oTd#wI(%__lF9WNwEZZ<%0g8XAH!2%%4fAlKUm9jY}=uz-`G z1z>pu)C(1AUqxjilo*kD9joP0W3;0wezmH__f2N6rbgx8bb{y{?A??)1=TlI>2nk> zqckWP-Q#;aL{bElpwY$DO_h@r;NaB5Ozg8?l$(Q>(k+t(TSgr9wSmFD9CuAEDx746 zK9RsXQ3GUZC_JpX|5TdxBLB5tBOi<5g10HQ7kb}~f41e|Go6oD^8G<{XAa)p(2%@k ziq|ZfX1E@COCwt(AhPrG-!p0H&$S9?3YzvaI?u*Uc^e-`R9^odbl!OtgztX!G9Jpk zt3cH^436%Fwv&;PJ;CgPwRf1nWX6*Y{p(qcRiibjnk<(u)cVmX!CtIMR{~eGYvxsi zKLMN#nDu(}v1_R~%I2Gw^VC?jA_UELRyp@b>pj<$mUsd{4)c4=v47 zy>Gfgg9`$klxN3<5r;qPF(*D`oheGy^xez~Uwukb3|qe$b?TEh7gl95vMB zW$LO7Txh(OructP=#RhiFRU{cB)5*8Ol!nU7Ic2X;F=O8szDOWwZq9@gBek}3kDpd zsdHPhU0BWI!$nEE38rN9Hiy=UduOFDPh-?~0e=IY;2HGcWa|qgbFC>Ax%a|s^IP@4 zsDbO7`uJ;VW_rTlEg;BI5K8kS}F6%zzI|y{I z(B!?QlkDzQX`Qz1mHsH*7FUJYGCDiz(JtRICd67VbLwN{gV>ErAiRB#F-96>Lw@=(C>F~mW$veDQX@dFFB@|U3 z>aWBeJloCq5wnA=krAiPVtzCxraq?^SZp|V_=guL!Ag_zwAqF+ZktyUQ%wYfjG)l} zfnMY;<&>=nEi6Qc)bJI`ty2sIX9;4DLl+Ib0B;D375={|y+Bj5^xs3T8cM4f6d3Qb&B+`3uIkqIBAeC*zq4Pc0nB3mbAC9svltG+qbgLVZ3h$5r zUMp#ls3_}3Z1mR2_Ef;WjWOvQZWaEKLX8TOz(1-Cj3GG17ED~1Z&byFj2!IzxS_er ziEC{juFrqyqzwaS%lXq=87bZPPs$dfvh0*+iES(rOX@LH3vw6DuL#9}gFWKEoWQKn zKII9wT!o%W6v;Jqc#b&X4lMqCeEf(f_@RUT4Xgi@Cptydx%=5XMxUos-HT7jxD))` zRie18<&Q6d{MBBG5SE9pmp%`@7D@Un5zE3%EeR*esQo?wlUiFK;XQD!rj##YQgbR& z2sMk3_;_yT23ZR+PBV`0d`otKfkyzm3D93n_A+&HV4c)9NlN0P0lvj2cb-H$h7Bn^ z66<|s&@7(UlfRnJ9r5vJpEOpo%UgneMq`E0Xk3#R&${K08Xi`G#i)r-B=u-V5XxVA z#(f!@#J}FLt97DwR$q7G9p7lThk;_+iS65Dd@7EhFx4K}2_Qy=z*;gci4AD@)j%r) zS;>MHqO5k89M}D!@lJ_GF8pzO2&i3l><$+Z#mila<53>3xUPE6e-6*4e7LDm(j6f4 zd)(fj&6I!wg*c2*f$09y7@q2He^lr;T07yE-ECyjPa3R`hCXOXJo>n#c#c(thcopf7o%+6 zV->B4wmoDcYPzEc+G~_tOVS1FM@j)L316Q_|8CTT_nY(OOZvb8)#NW_n@d8g>K}iG z3X4ND!~oO!y!g^^WHV@z+APq6f-1yRh>_H2bm{fDyDSDzpPO`SFJ=p1tR_wF!M_trox6n<3$Er#Dvp{WwKVM^*5bQxd>o z`^$ie&t>re(xdjyHEDx z&*S(17&-nDtGA;}lmZ@>y>RuoL!b6xn63s_HkWNDIJ@B-U}=Cpl~(Ir#^t_>@y|M^ zxqqOel>~O2Ggp^N3A~>lG^+l{ol?B&AQ6QmvElaMCB_cS>_boAGG((j5u75T%JExJ zXGoz+g#2a)o!z86DLnMHzaO|HjJE*-onv*Rp;TqaS(n5W>um2S-m%uM{{BMq!l`Wl z<8evgi|g}`P&|R&rX8G~pYL9rhhz6iygV5JJ+5i|AEXfT4Zr;eCg(oukGITH53>=@ zS&`*hsg35s;l9Z_8Qmy&{0xX@g!crB(B}Gwj0}_uX1WQ3b()nDdcGcE$Oisw9qM=0 z1qOkz34Q?t7r$1V=tw{cnKJhtZ**U@W%97$odsMkkJ|v3Z`eMSAOpweip6RzPFwRu z?vax<4Ve|Us0B8Ew8DYJE|IU7<@ z{z-urBX5NqTCsQ2yQQ^O2sy{Hoa!UML&^RJArxcx z=aUb_2?(oaOUYOg6S-1FL8iHaQ3N@aY?#%kEk5q>pRKf;QWtw-xVzhvUPY3YiN1X# zY243I?eh9-2}?o6Ef=sQ@B17#*T$(&i5#W+Q>eCRG9nCtl>V#z2?zx2{dq;mr_Z*M z&)ooH^I3QIP^q3*>^7d5TGkuPO55UGk;xz82j<7XnZm9MlJCS21za;q#|`GP)rB9x zcng0~^V%4AJbZ30o$m(OO28P|@06=6zf>~XbXh9r@xY3-m+OmX>*eoxzEiPuV39yb zcg=19O1dlCOu|fJ(6&m5?!wzTjC|>1Mpu&s&=u@}6}c40z3(b1k&f$7S}r8#*!*RM zZ{L!uoH{rXAh0QYAORVu%UTK^x9mo;jXvoON>=DU*-xETJhfp6JSxJOTCh z_B2Z_w#`(t95+fv@4Ok?gz#yo+8T_Knp~p)f*p|JU zb(9!&cq1i{&@ej!!U;J8;0UVS2%vBh^`mDi=)OhUxQI27m=LKVYM@c@hVIlv!;Q@%73HkSDH%K?)fvP&^)6R%nK z8l58TluKC{4}a2~$w!=?i?`K`Y?ZWmL&W9MhzR8lN>68Z+i;58Rd$`3M@~5R6o4si zC_aqwvheXN&H-*vn&On0wXA3_D&G~D_xgwy6V~dSfj|P3rAU#Y9$7atJ2c@3GifNz zs%acn?GL);p?EXI`v5DFZLhtTR}ZvjwPXXlfw|fV=!8eE*x21zXf}jl$ zcx2&66KLi(i(E4+)d#s*vK#5k9&3CK)JecJtMSwGM-Hy$J%=53nWYwZEBuDw+qbUt zQuF-4;^NmJM=tOSjV@vi;>n`JZM%6sC9^mJy~~+6e~l=BR#pgflGr5^ZE-~@0#f;> zIv#NJwI$s=d2I(DBVkRBM_cUqK*VG?QVxQ`6TFEMRGnkTq%W7>hzZ#3>C4)iYr%fB zF$JosP!Ya8a>7lD<@z#5tCHKKE!N3Q*2eYmyOtKV@6gtu1K|%^ocaL3nmk=_T=_6W zLO5J!kw@87QAs=!3lpj-}-{$1P9^9kW+HEo3>?(XXrRn`EW?$kO0=Au2k&dk&`qg2uHI0S~yL<>4qn`d7kE8y>I_%sh-G2jhU#+= zO`SE^f*k`x;^9sUX%uU~co7-SuQ3IWl?ckLWRCI77Z{B5(=iDRa@-S*aGE=v%gHZc zvws~#kQ^%(*Qqe`fSUypfrD)a`CrhPyRS%0tIILXdG#rc)Ft0u(p}8sz3fQ#1?J*Z zw$+OE;PeT?ZwAI5Wl_e3-0PS-2x>yX{b7g0L4a!3DwZW`?V3(40w_ml z10lN+ZheE?&jHH0y~hX~p@!tJ`$FGf$X75e?oFPxQi{D%^qo2W#Ui$}>3&a3$o~97 ziNE8{kDvvmQ$BYp;7~7(64$HA(2xk*(Vt@g#jazz02EZ%v%p)A2xM%1E%fKu1Bb@a zsn^(WF7a;Xw!ihKh5QD8?DgHgt)!lFoFs~mnCu7u{KOKEnOik19;wm2Tf6dl7$I$3lJBEnq1U zZD=nbhCJzOJ(rypfB`Hx8=XJ0HOY`SPReh)Ii@=4Vm%@JIpZ-75Y9MFIekU^j8oV< z-RX1U;Q>-kz9|uS#gQHVj(5M_z0hgD$Yx{sHUqrcXj7$#&Be#v_b35EFFWoFxZdK& zi9~T;ej)hcXlADI%-@EHd!ZfsKZV&07t_yqp@IzKB1;bPys|<$2uKoievXm=#<*It z5rVWzszHVbF*9Qc>8Wu2_p#2I(U9l7m4%0;vmw}C*ph*xoAai z*lJ)I@YW1k>*CyHA{Ya0y1m)0L`2c zH=o}(1LtJ7c0X0rKCMGOsIKVLtw;{ALxy}*N1IB~I1Sv3WihD2Xga7w+IGARkz?jlH;{Tf^l$$`akGN5dn$8SUh z+lTMGa1M(wPEOk9#(djOR%aeq92w;Mw3){@l3*mHcdB`r4&2SfEhSeK7VJ)a1B=sq z|E(hfm*U|A)j89TPJ&AfVHYB4_9$TcK}g^1zw7vFi>M9hcdI&7M7k}9UZ%m=Y~&PJ zTs`tH7L?^et8FGns$%_3N=P++;?`lkRw1xB);;X+H)bwVr!eqUN73iJcqDb#OJr+l z7J!-Q(hPs!SpMCe9c@YG#obNuI{YgdH)-n<53m9^-0k@nFVjD=|qpcqsa%U0_M>AiBB4T-F*yz4t#jJRs;WEfBB7L zQa58my3U=MXkP5jRi(Doe?E86mfliUcTK3k3LN;*_qry&nLs;$%9!?P55WKTp-Q>1 zYa=u!ExHaWlw_V_+1|+x5Kb`v^F00j7lGzoO3Rkz@B3-Yaa==d+O&Wp@xPv%RaFY^ z@F`=Q!=9EN6|{{bpW|N*0q;xy?r#ehVDn3w3zw(;)hHNG5n}m^l+XZTOUtJZ0Du2# zwpWr2|07XPE`|)wBT6`)r!TVHR&6&)e9OVkmfq!GgSel!zT+$rZw!krTG zxQTyU<-b@($0G7u{W;C%lY!Mw`vnnK**fE@on>Ull0)YsLoAcCz-Ar!U)~fS%9=R4c2<8>$nTx9_&ND(5V2~E&5vAS5ank2n!c$6%W4YsNb)9seP4xo2D(8 zH9rKF0TUh_>eg2W>{~B_RzuC-qn8GZ=`{(sayipr^efOh0V-}mD!H3lLP$a>2D`W> z6t8r+s+}(fH({dLhidQreS9^KA`5J@IIeA8rQEAdV?&1EK_NYiOE7`t8q_{h!Z;mGvjO?b zn*8r8{UTIw!k@_Bl~vCpXAfMnCH=waDQ8s#tWCF_M`T9NZCU9QE3mz&obFwL5)SPU zE`7k;lvH4^d0SkZv4*Lt3X}q}e*Ty6p}`-2^|e>8%Dv#JNTi^xhkcg+sjhCj_tLMfXROKX3&r5zK?nQmbkpV`3fZ|f zbVDKa`T6<5vaaAAf zn)R--0fdwN7Y3|Tdc9g#UJW%~LQ*;SR`W8Hur@^z)1;)x)RVeLoSuzf&Q~eQUPE5= zh83B@BTlSA!pN!qXSj7xrRHeiK?&ZKzy!>Pq5>*cnWE8!qR5^x0JR;gMFp24gGSX$JqWR+!CFxd7k-HJzhwgs5T0EpjSBG>N`>bUon z#2j7)tz1fWaRx7GSi3@CW)t$FB)tR~!NT7Zz^DO;&L$E?d8{j6cn?;X5E)p4;_Z{c z!LrV0m4Izeh!8ntD`@;r0ldP4nYS9<$L;r!a=5~Xbe%S02q)_KRC1zDf^9EEyOG1>5V;j1WMA_T@`#$( zUlVAz0TRIS$$@P&w(`2wyNf>9sRO1idN`*8BA@gdHj3lg*=EiG5HT?-gpJwI@UI|n zwKrcSwP5Jf2c|;kI8gSsSCdCk0`^HDiqd}xQu3Sb9P}k3ns|$UWr~)m-sR?CGLO?n z_?kNioX45N8G94(k}+H_OJ1zreG)y*&8vm!bP61Ad!@+&lVBV~OjP#>DED=*+L`VH z(a``@@RA+%tpqK;2GME>YeFKoPkF$oLbW@d$FALj*0AoB0OEDecLv}>!? zDtBce2(>JGQA^-k+fxfFz1V$gi3_AS@)4XC>1B%iwViVa&5+|c zUzl(1$}><|DzIsh6>BrqqExSK=$Z--tVOX`7hCCBWgYa(2oa}%u>fz)l*+XcK=^99t0I=G|=P4$xr@8e2dH~Jdz>i*O#gg?2Gi? z3IS42KJF*G;I(~JwE9uspi$Y}NUqDs)e1=jVZd^o#vi>ZbHEf|6iaM4i)DkwyDgWe zxf|>F_hJQA!$4^%c%5zlJ9kdixSnnrS5+TP`btf{Gyrq$7W{+o(~P@7 zbv|nhLL@YZ#pwg_-~1Y1FQ3a-pETYXR1Pgw7P*umsO>d3@^xfb{s=5P@uAMzOAjak z&NRL4RLwLOu<4+jGYZ_O4?L|WE=@88ok-}UQ=~L5d4wZ8Z1<^B8&$HSPfNB?4~r5L zWZiY@Mli73Fjz+WCN-%SGpz8XQLauOxHPVWElEKvuAM?!zJRkRpg3R+Pnf0mKpl^(n@D_9FFeFX zUu-sz`^Ve&q*s~P?CYo2D%Y+PFVfy)EPXLv#&KT`2D3K-)M385EHD8Ozr!Ym%c+{l zrI36OmX?R`YVJyJa*vSalOf-rtFL5`#gp$ zr^AhVgXcC4xiA1%UmJJ$Va+N1WrG~hk;OTg^|d}T+PQJ>ntUe~2IqFPHE|1KHQN+m zQvppM$VLKa$Z3za4ETA9AObeb_B!YM@+{wECj*Zw6Z}hSFQybS2q#)jZ3==B^|w~p z-IXWrKAy&bkN=bm=lBVXUn$qURjK%I7C$&YquNa;Pioe&`En}k5i*w17qkg36W@qv zV2^nc15~Ya)8}X~Oc<`8!UotEXZO4d9_W$zH|#3-o zST>N)6APD^Ra`Sth1ILNzc5T%0nUe{qF2)J`?7V(6M%(Kwbjd_rjxS0vb!tGW=1-TQ&UyR}BOr@-b zokdGm**!6Hz%~QW5hYw(RJ4#=NdQt^*s(FM#wdT#6p9OSNR4toI3&&E@_Ds|7r+-V z;(CFAs&RJoJsG7@Lu;1Tm@}%{`8{`>cvL^r^TD~OD<#Y2sRQj_oniD%Bfpa>00Pte zr}Sp>FFcL<%aA1WQTO^YWJ-XEAZyJ&JYAGouuF=32E1J+_$eaY=u^*Px&-kM?&*xBT zww5=VNrtD3?jyRo-RExz>iwl6Ae=PdcN3@y3hKXfYuw6e=#CJ#tB4ck(+w8oCZN}c+5DxgAfdsQ7ZJfw=B^HH)ci5 zqw0A!o*5byiSMr8QqKK?KNwxd8G4PekIwS($V_qr#zpxx-4K8n6Ur}8pIK@`9%lOIv1S7gNMq>K=s_9$^a{kNDr2xFp95c2ye$v19bZw*7NXF;XE1E|gbRfCGh&?qc6e#kN)7cgU zEE@wV?!{IQbomPf*^8?B+OU5#1}IJ)YTycy7<%-=mK6OVwCff_;zoP0%A!-=dvwZF zwLH-qKr9ZGB}hENZ`ag^HrY)|54Bd(L`SgZ8Nbd8b*XIf0uO|f%bq(@44h&<;AhY_ zK>$;;^Yqv8jvQL7wMZt_^PMB0hW?P+TL$z`vei;7k)_dcb4N-nEEIJ92q@?tX|X zx?O9@q`g7v*I#ZtYJ)s5GhIm_>JIdy3^GylDkIHu-i0d|zMWL9(bno|QsaPAeaGJ~ z^h~&3Ss*V7(AJUT$AGcF@a&_}hk|*T&kCe4WB356v`btp{TXz->rG1oIQx|hp)c5y+-TUk(8Ful? zRC;A+~74AIjGLPpABSBuTqaaKL7US^ZJhAp800oc? zOqkmR4v{54X{5K%{mKzUfX=GN|9^Z1@Y&!>Y;66lT3Dq)X^0r3N|rw4LJIoJF6A7< zpv}_^md6+WY86+Ppd+q=_hv3TiaVTKzIPr{l=l{fr2Vcbp^M49!|#AoP#RuqSmfaj zbhbg7VgQuK42MPr9PUa$qTnUYdw)MpF8Ooc7p)AqlUgsJtM>CL8{gJ-?*rr#xdy5OXdCXji#8Od-JF zX9`KD+H9Is5>$%>uOHN)DS}faHrS8=ykhB#tvO6;G#R%6)U9H--@hz7pppvzqMpyH zN(#`Z21YLG#;~_2H5z0x4UFHQzI3yfj1iYrg`14CSo-ocq&O8sbeENMZvgk0!Bt@s zS6%n_FK2&iPy}Qh8;zrM{X(&Q3-O0JpR@|xQQ8=Ej0LMrOjKOJa-g-S!#_`HdU?3PG5&h-NV%+o!zZxQ-jnb9v{#mOW)hI8#|E3Ee zKQv4)DJ7Pg!Z31tw<*@slV-hW(}VqO3&IQK)wq30Ukf0njIPyn1JjiSue%%}?JtKsM$|3I}tcQafmZ z1gY|QwmJllF(|hH$Xfqrgl7ljMSr}gR1!6D^QJ23`Yo|+>E9%}1IJh?26TdYXZ`<; z06h}q6Xnjl3tlRcFzm-wNcs9I#GAhHMaft+bu&vvo}dNu`j%7yuW4K{qE&fC z*O)c^yrqiOt=>f*DIKs!%ZB&$V>bc~atHhl$CWTnE;&m{2q$m8a9!9UH55>$_`>yN(ADoilxsXsT|X!D3ZXde^Hkx^hzBb962fla9ltv+ z0E3JX?$zU%x_>y8fkkX_&|#m+Hx`HV7#^D-r;)1^dV5a=qP}E_-7J#^R)qA&*saZe z7?jQRRR4KNMfaHi#vA%7wG+*cHP36*brFQq$B&$r6#Y<^3J>fVM!Cm78Y$11dCd2e zqWlVWzEQ;L1BmHM$i;1?R+_T8l{LEPtgLKgD(3iPS(7+EhK0uJSi3oHW)r$RNfj!BR`0-x2&H7GRsNO`Zo zxC5?hL*ixp+JnW7!uWIj7DNJgZl7v&9-sf8l-=~^DP`t=QZBfChjY*_8dma#e{O%l zih8z(n6qls{A)t2!IQ5>p7~!NNgSk9UQng?7!h?Vz+>kstLl zGqs}t!{B%ejaURMXTM^M^diT|RZ%}F@|3#=2~Q+46oETk;~fA~f=b&$bh&P1LOw8) zY)(zKs-kBZNgGXgO3&}qHWQ4kQamaZ@IS`jqsoYeb2Jz~l`G`S`hmgF_*)b8zPq^itnXI#CThagKD*o3JW3L2FC)|@L{F{;f^{bBZ-f&(v zTwKDbX{<5(EyL&B2M?~MuuDQq#MlX-FGXF!AmRmq!}u{SgDE;b^hS|U!y+?b$-=N@ z37`35+Q(;Em@!~?`UL{xgRfwkVC0Hj#NxQ|QMjf*bK6xHK^>dfS70YrH(GcNGsv&k zuKh7_C9FgG9;m`o58O^gwEp%AHcY&k3PO4TO(OyqZSJmeQv zKoDa&7I0Eof9r#q07`R2QwprB#?iRK5Xx}j|HIi^hgI1H{lavMNJ~hAfPgdzNH-!K z(k&sDb0&yhg;q=)YhpH5|(5-jU? zuS`Cbyzg?=%^5mi?y7v=F*jZy{7kJA9Vh`{QLoXQ(THX26Q<3fm<`SsDm}F>k`s77 zh8!i(lFh_*v-4=Nc$wHjn%`?I^BEFIJas#;#T;1EgH07|$#D`zEQUP9A?RCzFpXwU zS*6TFsV3)ED5RcGJ`$=iH=6nZr+{gb0)8`6>;>{y1&g0pMn)gQknj)1epIQN{d#6T z&U3i3yjJjM=kZT<-}ZXqJ#@?SOx4nA{Hgz3f@yo%Yi`jN1nxk&y*Tv025d6FkRjZJ z>^<&W+0~DFIcB^b#nN!~G;=<4pu0geaKRzb`QzDU@#lKsd86}LT(GIf{KC}Z`k*AZ z!w`=Sct}gkT}3hd(Z}BL`R)`RYm}%N`eT(>wf88Q4 zB5mhm>F5MtrlR0nBnIP#Vrw$eaRmatIyvEx@~%}p3*l*rB=7)lS?7)N@-efT92rvy zx6{&-W@7Zwv~G1Vs+5INuVRY0_jM~-QNW*dDBL$)F@Mcc9yl9dii|D)EiJRMKoHxfJbdtu+WTd^c22rn&*?d?$il&G;c0{e7N35Q$l z@9EuFA36$6LDNGn$`I-FJhf)*@kxXT>{fZD+)nboEyr(XZSWzWCasF7k`#pL-+%Pl zrTqDvh7cmqWG2RIl*MllzSh|4%KP2$HwT55vL#*hv(zjR$v184tC4^B6!!Ip(uQ9w zkrx{#7D<*QQ7p}<-}8nLiaagP%P|Hr8Xy}nzPFN52G@?Ba**{w;i;p`b{@SK&(jBR z6X8jesw>Vn%6nFUt z=UwhTCjSk#`mPIY)rE%Rkpl)*{AWf+fXf%5xq@7aW`XB~4?9GP$h%FuNCiijI2DEF_+nFu>yn#=IVe2HLm&z88^DyCxJ*zJ6{D zHlFXKO&_ZyO`I_8O{be%x*tCF2{0kq;BocG%}Ev9PC#L zHI!fZqjsxi8a6+J2U}qWtPoP-8l8pxFTI{UA(*mo!Y}@X0RkF)AJAn~lHfYY^&~1f zhw@F4Th$!6r=Q`Dn~07yn#-ZyT%CtQ=6sg%!%9MKlwYP!OcPJ?!6&a$*$RENqs@Dd zj>jkcDB!C`CJd?27jjDvyeIF4^8d8|d7n`ufsWz2$!{*6C=7TwVrc{`(_Z2i6}$cy z59TqDgZS6V$!j{exX<*;ok&k7T5r@2zqaM5be;ZW*e7&|Q{K7#>dR;9LFF#4V*?9p z8Wc(^PJrLH+f%Cag=8_|E6N$X=AaxD-=6o=-i7?=Kd@izJQ`=Ip8yW4?BNe}P}P0> zQjXnZL>n81IAd`aLLFUnBLvW5Hxpx${_x5BqpnEc=aWZ-g~*spZA297B%OF+4Deb3 zdc+|-B#cII$=wF;bRjqp_hN3(%XVTTF_X>qef}<``3;}0h|!%fTJ4q} zKG=Hfg+v_?Z9^3v;iX-8AXCziMO2vu@xgzpf#fP=X~wc#J1KJKTw2=rF2X`*+d{#~ zt{X780)!<6;XuPdA;#N1kp5IWOz?PeG|F_L(hv;Xm8Ol+MR)%NAIpF5{hR2v_>Ygpr|WBb zw^7TSoQe2TTW*+?#r=>y^&5pj)r4L~wB=-^wR@e=2RT~)KE&{#3+*f*+D^@qKYcSj zL2pr%71caWd_FB&j+B(TZLW=f>_kt3dPo5`fJ%|-N_qA>ap9`09h>A>XY9%bZFj;v z^SE_))6;PNhDiwJHV+^h6k?&)zxP|DQ#!!LF})mA76yvsRPVq+-yQGzz%o@S%o{G3 zV0%egkoKN&y^8w5p9Ay?6+%Dof3$Jr_%~foz%Tq?Z5({2tvEI^F*4?;5OTJfIqCHA zybRN%k{3Lk|M{GRt>Gic&p;IWrZRb@@surK6A=|$)2TeXt=!#==78mUay0;M^->b) zG~fVY9U^U<6zu6I8Ll6t2=_QCZe%8?FhZC8owMv*_GvXX zH8=8PvkRRla*&dx(vYHNga~jSg56F8IicNqpZkYFz)Q)nXtHUILAQFs<8`#=m4l4Y zvm2S|A&MCv5ZFoz8a{mHJdP4>>jd6xN`&uv7C!!2A zjMhd5me?`?_XMmscst{Wlu^K0{YW87c`|QYm*|CU!sR&}ZdoOxDmJp;K7Ze!tdKM6 zMrJsnGd7WLy^f@etv|ONQlHQpXJ)fdALcDcKf>8+2!jrPy9+MMY4&((eba2EgtBng z{9d&&zueUbyP`#X9gx8tYZvqosC1`K&|ytaU=}5KBl=KKtXt^gZz!$fdF8MWMtOb< z+xueyaUUKA`?2i#sk$`JpKpJCvgh=zs87+9Ggx5ffv7IDm!! zKpl<-?EcwGnfHC`;O1+5pRzyKMorP0WZaNKsl)BePu+&8$C&%7%z|^&*pUN+C1x!F z2MwP~%NIl@U+3?St=hA*=3m`UYn-PHdQTg8sNpb=nfjIjzWXg^T-kEgL^tskUTiz1 zJ>F3@=)8(&=eOR}n%Y*vw;M)Xa=Q?@Qoqf2QrN`GR>H0WH(y?x6Du$DKRc7rddvvm6*`OXe25wBuX+LGrL7^SJo4_9ptfs)_R0*>6;C~OJKOJ+XBUgHHK z8mey;0s5);%XGdXN5KeJDblxp7?oFcb?}&`mb<|lL$BD^gdpsvf@Vk#kFeEd&34s$ zbyMKZsLH9p!$&XNus^#SocUP7m?Z@kB(TuZ?@Zapkk4Q7+-TI7=10|LUqaq<2DvFG z2^AFa7kWg7^tH8SJ7~505x%IPfDem(=sL=Y_iekE@z>z8IpeXGUtSpsCvUU7&o1FN zr(O!U^^mIs$})Jp(P^i9?6W~^Q5ZS8MhjO*3E{E^zGM9Hf2;uc^k;}Ug!<{lJuZ&I zN(Uo$OS(4(PHv)9Z^DW}V5OIkzeDZBx9^d-870wb&p14^VMQX-a%`9H2VosSi{bxA zEAYXW=)eE}f3*UzSfjhuVHBU}JC#HsNNT*nq za4qm<`t>BJX@9OsTJ+gD1`f)SJjrsITXn}|+9$xdNw4^l_~II92z)3IvKdiRkvyLd z2?Y4%m5O`N<(CgwQWisP7D-JKrScR*c8MUG&`{k~8&8Zr{|_yS8SugVvd~D!h7=4v z41h#>t5c^T^aULhvjPg|K{`robtP|rr!%c#9za_PAuFbJtx3J*?c2*Izv$a|KO44p zV(Pw&bYPLAuDWD)r(Bak#;ZJ8U6*1fBzauZrSZy3IM~dRYutaua#h=v;Lz7!_TK~{ zw&u%hJ4p}sD(k)VdF6KWjy#+{5+9iOW@ArFtPp5ENyYGN}RP&`P01cEdas@L7yoN0#D+g6T$cm3Lm3rI12-Xkh z*ahir&41MsA{K$bTwo+^EjL|yzwhji&Oi716APJ zT%K?WZG)1u^6Xk2Zh`?K0HHH3O~|U=(6R1HhobLp=&p4omiI>@)boz@D$wJoi);u& zQnx(eu?TF&{HSN8U$h0oWEGCnf4~yF&(iU-J3?ALguwrEL?jt06xNcI_|=61>*FE$ zYfqS7-@TD4U6Ss)~9?E*j`22>SHK9PN;vopa-L1*D{s| zkYwIgY0dprZcVL>xJrfd(U@1o^(S5*O~Xm|cRvmG-aA7`^09jVa59!UFm&pLuEQov zD8@t6j5#8_-KM$=dF@$UVo2_OLlSW%c+c-j)<1xaz+c_VXgSIx2DWJo+imZoEweZZ zBXFsk?qf;jlZ4E8$|yOyxF#d;2;5+z>*74PRue~ve`y^DNJ+#ddGkcfw2%;5^1Cl= zwBX+xIW~2vuHn(^A>#5qy!M5Vl=}KP@X#pxqX`mMkv+MA6t7$ub}EIqSSD|bj9y*& zroSYYS@9{dR=Tk0L!+una*Cm7(R!GYev$5)mdlBk!Ar+p;BIDvqp;RQPdt7$J$3im z4`Ldfw+Ju3Yn?kwtoKW5$|wPBVoZ==Ej@Z2DuF{VK28**9Uq~cq&`V?$i~cbnSY+g z3ZY4T>j%@UK>(f7mh*%t5{kt(r*=Ey{-85RC;knaT#VOq%CeM^bSk6E zX_@GS^7I6ohj)e*D*9jKDC( zC{2K*UY~4{vS)r1=M6q;QV~qOoLKalYs5=bfe)@n%e}OYAS9tL;@>oCK{mrLN4}bS z%+_?RM;GOK4Px&8a%*QGFpd(WVt?Ziu#`T?l_|XMFX>Na*FEhFj?eDM1nj}*C1~gf z`3BtD+7URuOra<+{ZOUEn8?m48>r}Mv*{S9ZmMeCJCADZv#T3JAQ?G%}{KoiOIyBSKBKe7EC*ed1ei)U;I#RJo;sgEe zXG{=_3H=p|%Xe_vuO>Ul5sQ&yhWyuF%<#$r{r^8|p&=-Q(zgEEwLQ^P1`Y;Q-pzwG zLAA*@5dft!Hx2EO6)j_17IF___=}!8h+&MTi*-mR0(TkGmaCu^2D0LnUr3}?pe(qW z=p*7UAs?dC?MWBlE%fl0x8+5*e1FlTde-c3QXPe%yzjn_`fUJL;r=r)us_cjV}ADt zFe_N&*yO4(T6nNm^3IPaT-t1_J;PMW@%zgIOh1{Nj_8%Idh2wRFc$@X zaHvJ+nAO7c5VBW+8NP74wLd@C{ZH`*$AC&)!@-ygkV5zf#OdYClSwgiDKy~~gKm|& zJ%o%!=;uM>BAc)N))>Cvmj}Bea`1N^0-5zd4R|X1wEN+iUu8RO zY^`ZlB!W4U8!bMMNWowD@}J(r!HBMBX&=?9Bk1$8pK&y0hXYYwlOn%W z-Ox!3QqyrQlEgolX|19VLlsDOHWg!1lrFG&Xz|M-3M@r$N$m$A$>y8l@H5?`KILz0 z-$r3fpLo>Nsc}B;+oG*>G@y^ChR$!kA?d|wJ^qi&aZkV8q&fw*dhyK$^vdF|{oJt^ z+3vGd$7OKj+)EWd?%nTWt2t4i62m`JMqN6s3t!Y`*w&dy={7F@32miNd80g<9GpG zO@05TItWP)HS5`UBiy)-S6*FpBaVp%-ekmT;dsnvW5RYt!TVAW{l4LnHN~LytF*C6 zK{nCH?(3*}CGW}!N!DHW10LXn>zgJO0F?!C>GNXD%hr|0aNn*mT=R2fEpoKL*uUNc znzz_IF*pQBsz=&-zw#Q($qa*w)RGBCuGwM9FpA(*VvD|ab$u0-3nA&5X`^{bALs-g zWd_!&SESTGP77{;{VFIv@V9WoISm%mt9{eBG<@ZXHHKEPQt%)F8neKiOWZjTiC-qpFxjag)NgTz8|>*0%*Aa5 zfeTMx@gbB!jrVXs_YXVOkD1@@R}0vRhEwp!$*O?BoPVM+SmL9uu$c!j`Jq+D`&^h{ zU!zalR%E{NX8Q`|eJg~Rm=N=PR~~5i(4@nKAyl?m7DM%<`Cti*TGe)MowG;k`(Za5 zL5dd~x$O@s@j<%|XEStX9A7Uo-*ZRFNX7j#X8YQ!3}iXni<|NQ-~GNasd1jbrs{_k?1zQC7(IkTz58&E4MrUWe!=eo8(o3nb|&8_(&>R~*h!QQ zpP%)&xP6!T{Ry4-b-VuQLjRyr2L;?S#3hx&r9+zZ(t9D93y6iQje+-*YS(q2dOI&; zQxg5@OoXRk?Sk?YFyw7ipU*8)&lbmb2Fpb&pY(4fuE7WS>)aJ((cWEo2Oy@e){ARy z$g#OofTX|r0+;S?=Ho$J!T&v3Q*UaheDpfLLl*NXzO{mOIno+VKwA_Rsl2kW>GMq= z^t+Al;D9r@5(QS9lw^{3+-Rz4x6ecT_#ATqlZvmE8czi{<5JUP_~lnMvcnI@@v4`R zrIhu+I%A9C3y1O(6e^L;f;vFyn!%6xKL5-vYQ;9GgAb98(k*jj8N`bg3Fu(K#(bR@ z0@(;iE!)r;2T+yTzZa!*i7kLJr?fwRuW3SfsIG(p1QzD~K>VN5A%8xdn6EE62Zs)? zbDzr(b<$(jgTSV|H!OsB`y?2%Z>@1z8$7{S3wA@4>Gclpu7IjnKC}lN`YR6`*QnF4 zF_@d5G$TA1x+B@9Blq+*d#`rMhFZ z38Ef?V@2dzej+QELb& z)qKcOfWT{s^tSLo>9*}*hZ=L)j@Vq0j61Y5VOOaKTzIUOy#0nQiO`T-I^X9y93>>0 zzQ-j@>+A*~Fx%_9)HRV-@(JQNmd8fHB%!3PQIKWHS3DqigAu;{#;k>2Y^*WR-WAj38P{*CHc-Z6|D&PI$St}3$+N<8KV2ZRdpb{{1Z*EtNxw}Ura&hb6DSD^BuHRbS5w8QfCc)ZMw<`*`kBMGl=h?#}nvQ(4 zS2fG)s{?9LPZp(aSG1Drc14_*eLpD*jZ%xi7!Bn07UV)-F`FjgrqQO zzXM~SeWdVP?+tznDBc`2C4h>vX_B?@yYx0Lea|^#xK*{w@_pr30Hs5pZhr-5AmlOj zfJ`~}`VkjQlydJ>XfU9f2Mag1Ss=f1(4&}~@w!51Nx*rWw8jZhz&dMPY?Y&XO0$<&=BiF@<;ZmvLKQ1qXD8 zKq+OLAIhPK_bNMhVQ2G*zR~kxK*kZUMF^>Gzp>8ywQ9J>;S4<~0#K=Rs<6g)>9dy#kRcF4m6G7suIr z{cM2dIcZ~`fHn-eNjIWptF#~D)H57nNW=J?tRThKYVFf}fM_0}n>Dng0!5f*x4B9Q z^WJ{c#by>P{W`?^%OhZRXjrx$Dg+|nkc{&sO14L@<$jHzYadD8_o_|I#tizVDtBu} zl6#}UZKNHvs7VPUTJQUGFj{03f$S`-&K>dvh^#&(RM+}%zpzW+5T#v#{NZ+?RRRUu1vW4*efKOJBs=+z;gfPQ0^x{M5b4JV`dZK{pCL4Y1S zu`dGriWxM}g07G>2S3NGH7pRVP zSJ!Z;IG;TPfh}KO z-1nrgV!nF}mV&@V7hboN-ghd+G3U&Z=k+7lOe`43X!d+C2GD`+cDv$#H>+zNcVWVi zN@Ex(5@RrHqj;tz;2IZo+GrF6mcyOA{l?HXX;lqnK=!WX`zHp)!ju=EJ`w>QF7_^a z|2g*#OIhS}C({m*#_V*@qM*u^TXtX@T(Kb#}x;C|2b6PWE# za8sm&rRtP7?2aw*_b8aHWRPBHL{M?~kT#d-D^#uil@=!V9764l3qgMA6SIFrY@>bH z2gBw=13VC852m2yu=KALY!5y^0bGdwrjjE}g*9elUqp&CnWmpVZxg3QF3D=^AxCc9 z+C3l#fp60i+(kZsbvTUW`t(_O%8XfFzv9K>N^W23Rw4e8tMZDpECQ~QTl2y2jdB~uIQk!K-3F^=P_wS;gTRQna4il z6VPvX3xoObH*IEMbZ1J-YXt0>%%qS3QCuP2Zq!=Mb4%}L82SQh1piB}xkpZjD%N)@ z1EC}42+wU^-$>NY#M>eM9qM&35$hb_t6i9Gf2D)|n3ekeyk04?p5*v}TPn?ibRNL$ zb}HYlaOF#QfS5qx7AEkFLs~RQY$5LV76`1XPky@s{@GUhePs9djdmqO7Ih8}>Q=2Q zLEr*w=mIDfSGTu#dy(B$gB{^4Y2jEET7CA+An?Xh&)b6q+G1;5`P7fkIX-y2Yoa2) zAWDyW0s_wz3POVy2$luDQwC+m6tDD=1(%1Fk{B~l2VfA`$*S;{j?%!~qK3}x7AMO{ zw1P);cp2%h(161m0f*ZaL{FC-Mlvy{SLn+9qkh{eeMJ%l0paW}_3aAB4jl_aHa^&l zaO+xKk^7M$EsTHxZK1vE_LufR%6GRYII)!+TbBQ2`rCFR)f@!IbiH|d4?=0V?scrR zc$1*83H_HZhM>UQE7lVbm-|0d~TH##XW)1P&E7y``h3 zl7q9r0%kxo1f-aQWzX4}CI^@lA(6YBvNOItUwc6kR@pP6J*XzEP8-b$2;TWyxNg4@ zoD!Z4RwFGrT19c~N0e#RV`iKLff>Kvl?=bYG{#iWz0uF-CHDBabUFLs;t1eaOV1ro zi4R$M#^lHI@N1C$p6h8yAL0%GE4=-o2hvd47BSPT;CIZe@V4TNl!YoWn~`O<1K%*Y zO91*Q)G?$LVt#7oYepnK$5ylA@QDSm1nA#}2V%>peh8(zs56C2E9;m#vtLym5JBJv zl=Qb)Dq*AB)A%b(UfM{98|MrAnAxiUDMAPKo#a@Zwrm1&1}X_;l?@iI`eG&Yk%2<( zj`__IKZNTUJqPscxlBu+V6>D;J@Z-bCyxvR13~`Fv1QL6s;w|5xESyDR9|)bOj&CR zr#Q{ynQ@LD3QL_$;zQo?`x73ly7-5}UU!Rw9%l+g*I!^zY?D{P*AaSe1V!GTvy!ca zBbWW@Z0PiZm1#U~5PQC6{9DoZH*~6pT=K1%|1g8A<>9bT`9G(oHia!tS;4;YhX0~Q;4WEnTUq;YfGk2cx2ngB)O^YB|P z<%xyDp$eW?7Yx)dHE-}BDDUe7dxU;t2WopEJVa%Ig2IkW30tF&sN611{h}# zxI5j{bL+`fqb3dzIavSE$Xw>$*R;L zd?}f1#2@4?zan5u4hBntoxKCyfhH{+X4t0e`R`51Fa*yN>e3A<;_bj|%6J{!XRCGD z%8b)MPD}VqZjTek**}LB#(s~0p4;QuZ0dlCO|XOa!K6nnvN{0o8m>*O6p9Z*zh7KQ z%byD~Y^M>a|YG+{~W!~Vkzg-GS;G%N$bKI3+J&k|(ejf?ikE&2j4_c2F1tGZZB z8g0Xlc$y^5Tbb?q$KOv|<4`bw-ir1`GE}qQx;Qm^*(?sm8O@Dnso(9R7&{#Ud!)1p zDF6Ywm#zMgo>&%7(4~C6;=?qO5TKnpG(L;nxB4TuamSdp1XkXH)<%^tU2>);rAoP>Tv# z-WA<R56lp z3eQfyCk1N)lW$C<+-$oPgHbz)dM24U8^?asYhRK()v}N1nOFqbbVEwB98+mK1w212 zqXNSwmWO?ija>ayWxl?pe)XJexpf{N9}4gg6I$DW%BR%b`2K(i+VWJke-N**d_)7U zIu~H=hVCpk{`sYp16te29#WA%)3TYsqvYs;zW}OdFfmM6TwzxxeV(1o7*}4+1-(^_ zVy#Mu2on4FDLwcOAm>STVTO#i3~381ivq!`^JALTDQ#TuRTPdmj)NEeoxlZzOYy;2 z_p2cjjI-Xi)}W8x@LPE=+ltDJniRj1B+AMF0K++lqI*gfI$$g-!Z4JJznt?;{MtO_ zsBKu5@Z$L%S^-tjc-T{j;y?lZCJi6=KHYkeVc}BHabb~wu1eqr0-v>6+@8$!#nDjw zh>PqPYGPEq7lyI)IBehcFBRqXj+iZ#_jYCvE%9O}x~L;8mnkq+`sC5dEvv4K{0mwK zypbT$N_ahazm%-jrAgcey89j;2G0qLcYf$K&;2ZKki>lsypPc2n~VSZx%Pv%*w3pY zWoNJpf_uCO(T{D-d2C{)DnNjWI{FJzYO%uDaR(8$DWI||Pb-l=U!i0O)ia@a?yzY^ z319gf}fV(`3O7}U$-z?|aVF)vtwaImC!~?Ne@APc~WDLBZ*lz}V#t5I#`6`AR zd5FiY8j{pgd$JM>%`yJgH=)4#9<60*qX&Em)KF!xcv3fa&?bTTm6ky6<0B6=IsqV}O*KVSl&EqQq2IkbXvdV0MSsj_np4-H{S%uxLXg`s^Y7X^pR`aL zO+<|=p$&HJI?@g_;8q!DQVpqv6Efv!oBHmiw&twT_-i8!83WeO)705EzjpxM=9dr0+#6p9e>z6gXss0ghPzI#5;L;bSmMP4@~ zCrXoDel`mNzX*u_SA@wWW4?pbtSU3MS>1?148^gCN(a6ss!nQohA=Q|LBWt8y3tX7 zso@$Y_%#z98Vm*DE=>EaFE4t!+09;}lYQ52<`Qa!BY#7m?Hj*sk@*%j-h{G3WFz<9 zK5_6*dr}>tmy-{hR}}z&1t0b?*1J@&b4~rw9`|0CJVo1OKp)Jwx2l+o@CX4R3u z<4tZOH|(0n3){MM_dQGFq?{AdV3g0#1A*iiEe1julm)%)n8MTyRI{B4P0w@a*3}rh zlz}4Etr;bF8n59f8_2Un5r=6--RVeQiOq69 z_s@$ifR3J`QkK0Y>LdcKA^Y#ZXVIJYnK%pctOq?w_NuCV7EuKS2!DAOL|?WjROi8| zZ|KzVMOSkRP|r4LE)yI4Mxi-`<0s6>pDEpUW1`~?uqla1Vc za?rNugS{u5QRx592dlSGxUvRuPAaD$L;zZPy^$cHL5Docvk0{P?khWgnfob99ubbB zb-#KU;bC^$jJBGAu1?G;)fc3nhaV^7zY`ukLv6@8$)k37Z&vpC*S!+a_$vUQMh;~* z_UBhJ>%sk64H?r;)br1~s~yN3UVV=4t#)2-g#sk!i9?1Q9YV<0%(KUd378K#HoT{z zM^NVxVkTn)lnsJJZ)L1B?ejYSY@ex?=-#tv_H%+&nD0PA=vIPTNbmP)>r?w&?rrq3 zQclMvRPEjSPMrHn+?%^NR6)iAq6jwtMJx0gvtnszVTfY!w@c@n6shWnY|>j|s4E?6 z37Jcf^CEJPr1e^s89?3t+2mX%R9^83EqI9i zEs=gLxk9T=HvnH`hPXZ&EB}m4&-U@6P$!5a76)0*SVT@UumzpsDHZVMqEY;d?^BVT z%o;Ly9_?7DV5D#Gw4{&S)EZ3l&W{_dA)(B)+DN{x>x1j&Kw|~YIKJt3KxBwo=p+{x z;ZO;;T=@nn!IJWK5PVR8Sf3SkipUE?ZpcP) zo0%AH^sRrSKPix--Ab_EU-Fkg^^yc;GT{?OEoMd+bDn$t8;HWFCMFk4dlVXXdyvy< zv-?2WJB$5i?7n10w$4k5H0}{UULs69Kl%2)xbMKV>xFX7NUZUF=K$3by?4D?r<|Ev zHcjFm^7xNMy;Lbu0SW$-rzd%haVx>A?uOwU@h%J^p?G4BSHsT(#$JX3Pt9QHLPvc< zl=%PQ>D#G}JfktCsd;<5q$+lzKf^Y=Mtt02`|%*cJ3(>*X5$W^PNN_ChP`tj=bUQ^ zW#}XUhWu!=LGRK-BlE{ga0};PpfjpHxB9VdjT#jbLL_sUr~&?a;^Y)2*U04l|fZB!M4=-dxb^iEWS3ilqYbASKoEJ0w- zZHCFs4D^jMo7+as*>0&J-*f-`He22~jmKtj(23;`B=1?> zRr~#-UP;8*w(KB)C&5Q3K-F1wGw5Fdaw9`aaZrFRB9)5$;`i%5Qa=$S%w9>SOSVD< zRJ3a8Ujdfk46YE}6U!M_HRgVkhQ^a@{prI8q>Qa|3`^7=+XdJ=fCQ#OiB}U3m9gdS z$Jw@>mW8psvD|3O+w>g%E#Hqb^avR27?Ft_C9wi0F5L=Bp4D-(5u8f((3~BDS8;o_ z_4q6H{~v(alC!ZV3c}7o6J5n|+YJYk89@01Y+^9~uK=-BaWG7>+4f)cWEhpvjPAxq z^j$b+=`nD{q7QOYsVLt86kr=d;k7djm^|Wcbx>>IBQt5k*PATbbV3?5R4Mxk+(>bd zG%MO-H=|Lz@7Aw7x>XgE@T%%p3R~u~g$XU@YTeykpZgYb~d z5Pb@@zQ))OGpQ#33J_uZPDQ;#ctsY<)2R-^vl945z9J!ucLssLZ|#Qv72q%9gPpk4 zn2Bm+R@wbZM#*YP^7-5l#?lzw2NJ)~O8X%I|3uqaiNBe35H+Nv_k`3lakfcg(U}7U zWdevTBgrv@$9&I#;nVzq>L2UqxL1?~2T;DHXvGV#yfS|p+kLP8T?%gsr~khq{Ljh& zMVXdj4~^f|BS@!Fr;FzC+Jk}j zLYSpNt=+xxrt*ucC%QQ zda1gpyf8BNgq2eRi1D%+5-pV^X@fYwE#v_&^zfyRz$W^WXzZG86UeANK-d3~zE$LL z)r$B*<`pA*7myCZZ|b+MY*d;uUp{?B5x-Top=<4q#`XI(e&lw5U+WT%Dm0Gy`}o!{ z3KOU)nN_lQqHt`C2ahF+7K^* z8R4qkI;}IJ1KlOl(FN*U7;bWF0y!x!sl}f%PeTieY}rCTK}x3Ex0Li1W}xQ+&fOXRGWYwbHFhU} zko(7v#}k&t=d*@zg<0;JxdwvzBr#y}mQ|f*qc$%XT`0mjV}aXLAwDdPgFE;vFGx|v zdQ6dAkdufRsM$9l-Hj|S5mYB{d0sfS{(C&ygpA98_uaPiUkK;FV~7$bKAs&1DZhRg z{*qNMp(ZNtr_2DI*(hu$c82X=Dq76!=N@ph7LSprFeI*-^k(o$G*#wGtf?+q`1A2! zshYqXKlXI^jc-zyDT~+`N)Oyu13`mN)yZih<*)-8*Sl3$0Trd5|Cl3KvUy0GD1odb!?Ni;JR7 z%MD}ZiV!Z`0DLEk!*FV?w^VxO8l{0($bN2^(2|k)l7G{jZ#VmG18~1Wn{NEUHBL##zs=*WRsApTM2cVT8TG?&sf8uGNE2p zAlfVUWg-+HA^P2MH*(TsW;B9_Z8*w8wmzjZ6YBeH;0)pQb@44gV$8!!KQQZ0`_xqO z3IB`Lfy&alza&U%A?X@C?D& ze_QIeYQ;c3KRBzgJQDZ$?GN%z43LrYG;J6#!bUDfu$H}55djCQ_jTcOorlsf%a)G@ zaR_i_un@6lHsAn+T1xp6%CGW1ku7ZIq((e`5!{INQ@Ivf#vMDiW&E8T40IO`Q6tY$g!%D(d|WbX|L z`uMu=mV2_IV^q2Kh`g!2cLj_JA=d9NTD-6BqyBS?`S}r20zOLO4gt(WZ`%8wLp&QR zeqa~YZnfG-HG4e3xPDqJk9)#hX*v3FJ!b~KkURE8-QExZ6k%%CAw+ec2&c3&TwsSh z*GecnxU4LUJ6>#+Nr1qq>r=PqK+%!YgDGmh@z0Ub;&V?BNqfR1ef~oDi2x=z@U`s= zJ`COCFOwHvITgs`=9oOOJPh7Y#SBvY6|6ncc}iHAply)Q!S&JpVxDt}u@wIB1;-mZ z&05dP5i4L9z9KUm5Bf1J7U~k@fqG8a$Bur)Ik27F(mrg3?1AqG6u04vi-Kl0I+Bxd zm1pAG_q#M2Y5VO`!!5Zp<|S*-l+$3L083`&Z^?IG=gA3jV^3hw4Q4)ya3g!ibYTht z4>dsRVgDBv+`5VjoVW*kxSJjl5>j4%&15wJjlC_R_FdLFB0fx36EF%8PTuT7oZdwb zo!q|U`WBb0>DFPSh*bN7x;{PU2xHoZnw-JZaOC8nSEf_kFS&5Y=oz=>94&E*UH5F! zt_M9f*c~M)KLKy6!KHynk%OZ|&6w88oI)$wK4;Jj4eKPwlj=PTgk?IV4Tb_tBOrtP zER_7SDar8fOGX)OjA29RF0dlIn9Wf@PVdNa%Yt%}>G7ACuhbd?wC@d<59#;Ot(5&G z!BWAe()7xyvhQWZ6iH+1>r`j`SURd4?Aw$yaC)y4peJ#Ebo9L!juM8jqs|wz(Pft} zPjv@s(b6X;9ofHpcpKUnjSdVTrI3~V9P`mJoDEY9i)yzHj{+q+Xm6qsbys0S6+T=Q z(w^GkwkFhGN%eeQlPixb7Ez&8JuuUl`;wG`Hcxv~t!66OZWS-foLKMA#O#hMw2R zXaQP`$pjv!R$Hfek-RGs13pmu9NNf|KVax@J}*T-)6eS^@Ub6gB>cCs1_JP7vQaJm+mMwei5Pmt7 zm6qXQ_gE6x@Mdn(k&&%6xxTMB)H)jQuK>wo1#babQEGh%MLf@%ll{^V+AN#;b$3Y< znER1`4DC|*hq}yf$|dHtCkRuvn+lunIV})<1$|UWsry|-f5qw$mUVyrON1k-R1Pla z@$mZyhPf$7^q2NKq3QRcts1PC2X>W*^I)^e*;}=c3uA(bEgZ&Ow6Qf z*}dx6+nD^C5m-xyC8v0Qu&~O2a*NTvoDRavKilwvCLT4(LFeuC5Z#mef)A7i1 zsB39^#!ME%o_^T}&S1K5VwQu$j)z{*3%wWXE8H)S&nKvcmC3~Qk%oO^0XgVR9htG* z%vT(kEAtwY zYloyq)~qN%Q^7GSWC3)AqWHU0N(LP5AMwo}rp>tZQx zo$_H7H45!)PJE$B$G_4wq7Ls@6~+H0!>tQmnG2;{riMV5FLckjMxziy;jfIZ5w0q@ zg*+Vp#=vMhK*D^69V@M<_DwnMLW$U4pCVFgek$nL*#ixYk9M~ zSBsqYUNZ$85)tmJ)2%l3|04Ikd;vwMFFOJC<`9H~Qv3Gnh1qrUNI&6b)KN~oiZKB# zUEFvBp$v-9V{+=4x4cf|ToFE4!dmzxhSo9ACUt9QdKT5dN2N>-!-L=1M6h=Tj@|6j zUwz4Zt~7%Y_onds^j`?S`Bfe$AymC4(Rd1@^!fV5Q_?ry$id?Ia9NGj6ZSs=Q%a?) z0{>gBR>lpD6b-?y>WefbEPQ*x=KK=3`O5dJd|CjZ0^aj-Wu@X_+uhbI^$TvX%Nh9& z%xahXg)>r}&;A0+P=wl?h4}x`kHhy!%BT3z^`uaE8@+?gqJ$iM76?pyVE|bGMVL(1 zOddj`l!Im=qBPX3JtA&oLH-v)m#9-xsyfsK&-U(i*IHY|>O@*x1{miTSp#p=iqYhx z|3cXCT9P>#>DO6(k18uR?|LY3B;jKD{^$HxesiH|7G?N8Jc{4eLh9J%kOm-H#s!-(6YlzdIJP*>Tdtn zeTdFFfQ6@5gPDoc>ql3RWixA%USsRcNwN6Gn0x$@@!($wry^5qkr=MN>z)vB+rN49 z+>?>LUfPN!lNjrVIHE`^9B>SyUv~b$nwGP=L>P-w5spuc{KEo81zyJ}vgl<0({B#{ zig2o)D0CFmsvY<%1Rd8#(LcIhMkq-LV{A8Zv-Ci|WULUF^iBc)>wa~= zTuD&ocLhbRvPUl8dV@Z%jo|V0$XU|?W?0ob-S1vl>PDlA)Hnge}?+wzIU!8-u;Rb%T&n0%T%KDO0jwZ$bbn?r~X&|V_yg(=feM{~LxXJTxBK%uNZFP-x zlsJ$c?aDaT^`^P<4I~uC02=5m{;vRIB=6Bn)=OR3-^U-V;%&?b zY`jOrUn1NQ89qexk&D&xmLrP9TH}p4Na=tJ!!KhckU;a?uV;fJzgPKbTT+qVQrZ`L zyFfd?>dW3-p1VM;m^H@}Ohp&MvVTRmQVKciy%8a*Vnf;oLhq8{L#1(VGj3OD!?*Jw z@THf~zaq5LaxCM^FsyKfUlhryQ_d8~Q&dp?5i=F#*{U4VkG%UA!j5e%Sw51HHl$K; z>A)431qoIk+^5kL0e<6S7Cfz__g@kApzpxBjaNE%9w*U$r${Og z5R61{Pd|FO>&fm(UAY52SpE~Tg~HXKtf7Vx}-~xRzeVIknRTQmhKMe5TvD11OaITK|+*}=6{`o zxA@_C@5c}S5AS-{?0NJ;E<(lZg{8V(cXqN? z4YZ6)%RmS#yuco!#k}H+!Ns2&Q!P8%yjpRHw>~GmhQu_x@EuS>o%Ris61) z>K*%MeBAr4$~LGE9d*)Jfy0NH;^|BTv}*J;6Ju5Q%ud{-b5JuLFFVw zY0HnE_t%CtMuE*9S?<^7REzNk$r53R@XM_GR&$!|7oBNpGsQ1~vYLFdULqeI4sPL! zx*z8XDRQ1OBjn$)%)@tHnBb0TsqruX-t}Q->Q2IM#2Hga81emn&!9_3@$l`M`>R<4 z=_1p`__%-7@bAgcer=svf>v{;IeR+PBj4UYa~66gfiUOJziNo06`hCRygw}WiG41! zv2C2a*g6}H!S+T8e>U7lfh^X)G<-+fxTx0sDlR^^Nn}9u3CD10Ds~Lzg8{1t7!9X_ z$RZFbr*n2L&BbqmC4)f_f}b0NjGDc&pNCNH@&0^JDdo(W@UI&F9fgk@Xn%^obm$S& ztB>zqa$K=q3!((}>=l^*Rm16NYat#Fr0so;0Tesr#8ywMTWQN9_N>&m);~xIx@28P zp-`JHvWALj7BaG^9_JZWp+wl6 zW>2uBw!|g%BDM~!_DIJ5Rl&baSfI+;bN8J7rh*Tf;-R=vQJdJ}BVf-yhw)z()UwX? z%+^E^{6_Hk_D9oqr)2!HdA-v(d;LPZIiJ{UbFLNirkK}eQ=kwWotfCP^6^qcc{R~5 z^BjFqF5zBy)szxYk49bDafLWo3jQItTKSJ)h=c=F{OPuwn#F3g5hXApQi;P?#<;KpT`d0<<6G=mTpWb$^gvdqszGz|GzKCiV ze0u{KvtDp*B&+!JT0tl4hXlF?>Xs@aoQpIAmiFH>8oP*J29M2}h}N?|mIc-n0&SSv zp1oTW6nfs>ZthCYy&jYsvmaW1@Wrb#NgNP>Za4jtf`41^{_{}UZ>ZEKBCe}5%4-NO zT*L2ag_J${MgFe}E|-vt+*Ppa6w1GW^(okzIckF5$92e?IYFe&ukWo*+h0LAe{1~Z zt=%P7%LJ=~MRJ!lbXSq>w#5e;8IM{mNzxO^wl@QYg z`lvl?tzH`#pasr9$ej&aO$=klhknoRBBTPw(gM{O^0g=3q&1aN=9F^ZvRy5O{3{3U zsxHdm?lJ)Ha0=ODkg_zH3{LvNWz!M9)+U{>VUEt#=3Ll{Uxh!1yQ)|Lna9@j=U+`X zY!m*#q})h{It|=mFuUXYemmP}yMqfj8$)^^oN`hX9WC6ksK%mz{C0MVz_{%l>iCSz z4ST$r*qEz|72m`{F$h$-qNp=|JdrHm{L&uc(%4UjK!N|$UxAQ84X9W9%&t6Yr%mSW z4MZ`Q&tF97MxuPk(p6L#fg2HH>UvJ2}JC`-Qhag~<6Xqs2Vm);>YERusl~p_M&gubyB@ z#>1TuCt^Jf43D@wdM7qfgPCvYPOZeWuME4}SkfB6u~8;HDlU=VoArd7q8JqV=DlrL z#Or5f+%efA5VLbW`(387wlFOz?x(M9Lw#bjezV+T3+1w&5k&dTv35;5`IND4pz@G8 z`{{>$oW4v1x}bCF5%)0o1CQP(bcs@*6S`iqu#8=mX+$KhwraMa`}Hdnu8Y&Y1+Ko4 zC{`vB$-ofb5^}k#0`$Vp%59p?(0uvb3iUhaHE1guZM}oTOX^D> zsna~RV692aTJB(@I~RYj^JK!Ro3S3M=T`m6N4Z(6fMjo$hv-COT2D4XbA=@3vtCx`xURyE;E3o4k|CrZ@~A2R<|;&C#WO61t?uTQ{fjYFX2^ z^Ed!1;Hxp$WDPgnD zh=Qc)C@LnlpWX_$6q3q=xVGT{DKP{i9#>3VW_7+-Of&%D^XDwLXocLC*O&y&K97;- z^!mD?qa|c(^#*M14P}@s39U=JfkFo#V_tVXY`HICt>q})0y4Oc>foyMKZ^;6#-5YOeNDFC0Z70r6yK#bIB{P z17_#?@V{#WXB`{9)wq{XQn<@DX(9JHd%;8G^O>-<;hCeQIlR0*Hjp&Rr6^a{JKHC5iQQ6_ED-^0UPp5O8IBRGutgb4XnuR zhOU%I9z!ui13m1K{cIg$LqW%?R)NS%-K^oH0;SWB*>h}KW-`G+TXGLKF9wEW-3o3C ztrxLYY&qNTJEFvzGwUziTGWE*iXrwtm(x9;Xcm)NS(Lu`Vh~W27dECl^|>bDEp9IB z@sZEn=Won9MC%|AkBxmD`~_9&*KgMiBVLX*TL?zC`AGbJMGo@!+3^Baxk_(6J9WmD z16O%1ecbN(azID;({%S=?)UMqI?v~hNwfDhT$ z>sW%Ze8{5MDxdqf4q^9%McXj0RvPLX*EU^_*ZHP@;~v-hJX*U6*8F3c*bW=-7u1TL zV)y2YNyg`a)5+Fufr$w&#M|fR3AY6_B|iy2a(?pV`ylo!N1W%b8FHUEW>^h7nBwrsNMhD|I|!AcRei^yP7icAHWX~UC?JYND=!V2m^ zj0&L6Q~lbNO;Lmb>r*9?OW^|;sS|m*@NoPJ{9p0@?4Yil*=_NAbF}M=N8x?suCm31 zhJ#WqrdI3|VSmGq@JV&XZXxWzMQ#C%g66ZByf=MoLCw ze>R;oPVan16sZ&gmqD7!OKXg6_4H;#;Vu5N5JMg9?MN=i~r505UKYoXc~JCH|lV7Yz=SL>Q&xD5$W%t zP&yTIq;aT+q%ALPA*;<}i0idcXIstNys0)sjqn*4&Ft;$p~gfodq-L8t-86&50Mp< z@7KNVP@eB|<}y;lo#iXZ@m=DEZYk9|Ekuln|VUn{?@c(6LF;D7$!^UT*%R)#$2~x>>YA<}Q?tIpJ<4m`lsROs80Yc?Y z+um(XMVrO?Cg+H5wh=L&^a-f8z%fSZwEg@vFZa$MTbEdJB{HgyW%Z2#xbbGg~ygG`x%& zHRR^PM6)*b1ZDl$H%x}3A8FD>!FaoNo!kPr@1yW)b-IK$*8qo6)K(+&FGl$gsge+9#kG8{&jNnkC`U3C56s&gk>ZYJMX@^%i4 zT{J!nDYGDgrK+$N35H*b^;O}YWyZ}#t{?WH8uF=O0-LvgQGHR?*c&IC$gPbU+ZGL5 zihOcYBc&l_3~pC77y!?Ze9NR9sm2pZ>h!=49;dQd6R+g(dJ@`YC6=JuNtID%|Kv!7 zA?4VB%KX&LMD$QvWP{~AVec(e*x#vBOXFd$?fdudTles^ks}KCSMM;QMMoAVa19(7 z_T+}HLZ~1^1_XHz(&eV(AK+(yB0SHtDcdDH8%Ek?)D7z-ZH87ehQsgn5+b=f>1|AJ zgWMpe&eY1Q4EMUWx$8~57DIg16KFrf+fH2dA|So}2PGP!3TNML3uN;rgrNmDtCX0p zrS}N2L&bfkmdfZFf$Rh49ka_e$EVz0kLRu$*py{>qYo+)JpH1v7o9(CLqTJf` zdas6C{$KV&sRaJX2+C zUcVQxICA!lSNh4pSDzB1WM-*qK@D+*^aM8E$Wd@FP6DkOB#ZPJh(x*&LF0 zBdr-ZEy=>Y!dFaN$tc5r13cj{maBpOzA9s71?deO)AEdg%W%VF(*Dr>w5qHWG8JBTAMJkPEnXm|WBF$0zJvv-spYEWQvy*+0&kt;31&FuIZH6ooHUHZL9jlg`A z%10dVq$rsKxh`4Loz;l;_N<47uX5=*dfS>GJ@6cOG-8}->|mWr$x&L{2CaURc4%K= zOKP!7I3hTMA$B(cfUjIPQ@ z15HCG2RLa?!pRqwHzSAO$y;%QxEsILoBj5U*Fui7ZY;+;?k`1D7^BECvJGu0&L%!< zP$%{Cko&hP)jbZn#9O;}~|P_Cl-r6~t2CGBaCzi^5jCxkF3??+I|fCF7Tr z36aKKI4Uo<8*O*tC|0u*z#)Vqf(?C^)AEOUmUcvbkZ!l`%*9#Zk>zVHM0QO`F z-+Hz2VvD+%hIBwwNf}d7Z5)2UCyjn7qL_6S7@5-+4PRdN7rj1dOQE-%e>DIccgfn43fw&rHx%A}kyH~tu2rbq-I zNxQXMF=w3E8@qn$poHIU)#~efb8d1tHdaIIdAFKx^iv3M%!){_$H;hfC}!d=uxdtO z$r1G4APP_16SAviXPI1VdDvED`1+VzVDnSi<@(i&hmgamC+)4HCqEMLp1u*5xD8MD zb=gG+OEw>uVi?<1hhByn;ipwg+q2Ba76J1=TS_KkSe)dYo{PlLVtSAj`u7+O2~b1g zSm_8iFWU11wd!6t6xycJ9cCTx(a<0?9*EowBEIkad$#^KUk2kwfJ^+3bM1Q5pFM>C z5ml;i(!%mb;O}>)F-==(*!N1s$>_RSL1|6p*Bm z|AA6}R)#~d{;!L5;VK=!9&KVIBTVtK>rf&8ZL(L5hZN{} z5eIBY%W;!(Yg5mjG(dciG%)4U3CKBKnoa;$U!Hgv(SH-!7a?XOEHG^82#k{u>!aL5 znx&F55co{!a+P3Rb~4Z!AYHt&#Md?CQH^l%Z?h2EEspRXbB`JMN;qi>yg%|d#&C|g zpZHK)E%$AoMxAPSS0A5cgz4!8u}~Xx0|Pb0|4}Blaw*vlONYyNRkukg@^kNA9;bQ_ zYss!Q%kFd8E0ZDJYjl8hDKri?+2K=uel0@Eo#^t?r{^RkihdyyQpUMr3TyFyGAiL! z*)^z9{zs!$J-u3a{GW{GIUxR@?-x#TE0J@@kG<0tNUnn{Ax_m1De`9=K@cb|rWL&# z%!bo}6G1-PY<%ubqtLm5XNF#Wd+(jVf;rT6u1?ZDX%+~XaBi}duNwWu96`;`zb|>j zNGgwvE6i!dQM`!*Tm&@sZ?3JMq3Q18R9ZM9FA#gf6#qT6RemCJjmFOKw%>Ne56FEXyYHjxQYxyRz=WVGHoV{sfK9uk zuSdVmh?IkRwFM+M#$#ymCWkqUZ)lB(^_Dua^Fa4f|3#+rrII>5{R5&o#77QYZHjyk zN2l=8#ox1^od^ALYOgAU`u=a3UIpohx<7KXIQ(>4l&3r+te+dL^p$aURhw*upO*Q? zHiSyJ?=oV^_{iZ{Znklb_anmB%XX$gNlFI}AOA6G{n3y_S1Poh5LgIcfelL*?*7Egq&;^JR5dTSX(Gbn+v#I*Go(z5 z8J4EQS`ZN`pw5!Y)19W=9uFxiYuJy2yZ!U8wku!Bq6hS0Ob`ih>b!8OH{`TY8znJ#;#mNC#q|9%zB#1 z*da|Nvb2GjA#qjtcCK3k&1RWi`9;Yi2$dJ=eko^v2x?nzCmAZDVHOcV8<_Nh<%jba=z)HT2bK)b+tAsfvyEAmvS z0^YK+vIp)pS!C#uG;?!4xldU61#$*dwJVWCgV4Aw^-j zz1)!QilCZl#Qs7& zVZYKuI6;{2mXz-%%H z`(3Ex>67f=W&MfzVic)XXJuH=L%Ze^aL*9>G1S4~AH^v^2pLF}=d?`^E4|NF?_xcD zkZIOAdT)@tR}=NQEws_^E)o9Hhoco?asZ-A zB(Ff`Xl^qVGLQ6f!4dH9hUT{C-c-RlJTDfp7Fh<49w5ZBi4d0~o8%A0B}dUX7h1b- z_&6ggF~+75Te)y_b&Nng5B|$49#lYBXw40j>c6RbjvE_#pf6JO#&gK&^7*y=Na**3 zR&IL+$!l9Qaai_Gnuk0OduMMU9o*rZ*L&=s_tzM!Ry{c(tH^Z!AtRdcU6tAZ!+Yqc z%2U=~p6|Hr=pB&&fn!bh($8zoX0l2n(gI&vgH~(P0xCYYRr`yYIDb=ytxV(D^0F|I?#Iei z)@JGYzfxkh*pgs{;eK~%JH95x2XSHZm(PFk3m-VZ$E{8ny_8L+MM6F8-Q;&thDOel zg73zRBZ@>lpW2^UaJ^Hr!^=!!CO9j|z~{K{nDmb_Sv__NRG<8=9;~2Zy5P1fD=)Mh z)Dd7qGkozifs?cM7IY}P3TC)!ezLlt(7L%s?sxQ@WH*yCdX9H<8oOCaXP7uCcR>ny)PHk`?ynY>?mk|wT%u^EPl+JQ~r z{2Zv*L|5nPd18~qrjh3ZK{me>r^Ixx4UqC*deLOG5yN8o<{xFsLkkJZ@f(n?GdAaj zV;(rcH+iH>^;B$Z|GTqxR?1Kc)G4pJ$#l!$WbMlb%$%n%o?s*K=)y zUx9Lgjlg^AyU068_VAT>AuHHWR!MI|O&vs5gh@6j~6A4gYsG9C`L}~mmu?ttG zA#ch%wHB`pfuh`~{x%)c6$5R1K>RH1O-dQmx!V;ggN$9PKrDrzb+kr2ull)42nS)j=^KktWjowcazj3|I;cd^X%>sX&Ed7gM;!Aj3UspzW_-I*r;_dme{=2b-3`HG*U3B1f3{*MTP&&dP z*+ij)Pz6`+&22iHE~@_Sk-+M)y?f*-s-zO4>-;Iof1Kxr9zu~XFOMXKzOV#oT)eng zN5-zfEk0Cf=5me2hfBaADESb51+BMtErL}&n_<5gGlWKv>xHoBfjbT7F<-5rwG;aH{Z4DQfa0_ zM|7W(f)KZ)v1GVgk(Sl9*0ZBgSaJ|rQmH_woas6|q=_!Z57Y8i@7Wa}-$Aid3Knj6 z{x~j@W%Xix;EHIlhYrS^7@$m!>usCI6Bfh}S2qjYI4VUd;Ab+ljLiV5L#Qq@u(glX zHaQfhZsM#s#A{}utbeSx{tls@? zaTgMG$-!5BJvm$FrKXabD&-fs+&>ptx~`TV2? zJ3sk(S@$RXSvs zH@7K%>D@-K)KD^sRZ;WvRx6CNo%xAN%?l*OYLr5LF@37!i1)CIN?6Vn)58NhwU5k} zUhLfex@TMh>=i(Gi*%kwJDdqA-SB^T6VpFYSQ{-v_$}s>fw_rU2nrS>%$ryVuj}|6 z?cJ&BD%fL*qar%)7ue2L&y)v_kwCi!77Qq&aKo=N>f7K37-%cd{Vk7q&-^W!2f#LJ_QFV$;5EBZbF;)j=xo^5RE2}C99@{Vx0t97Pv6CoCe+((y@ zzI%v#Oav7l5;$}{6y1&15%-@K(U)LTJpG^>&>6eCCl3_RU}lHyhCmaihVB;zG&RFp zvBJwg-bQ>3T~dD0f2|;7i9=Cmz!{llepF+7m|?PFx%_kEIGfUA_x2GA?jqwC(4M%O zD)bHBoKtV6ovVgMWoMZT{!H@fOy?*6K8nl4ATmG09|xh*U@(h{O&^%MgCNAZg+gX# z*5(^_E<~g>*d#%f;e27Rq6WwsRUbN_@;B(C^Y#!l=@D!pWw=3)!;jY8;U z#gke4xE%x+uYS>62-OD6(&wSTdHP*JOoEqr;st%SCeS|rJ)=&DE8sYfli7B~a$gcbKEWmu;Xwbo&D_?Z&p zw17;6Ym57JBn^hCgQ7`-L$+#*_dF};)p zTxT{|Uw7?DV;!0itNW{)mU7+L5gv+q&7od$Fj16TGc23vA?rn%R)`waKKxbg)%h znOlU|^1*ZBE}-xSR4mxwaUt2TAd;}w<3qXKkTFmBZB<@o<|CEHGYSjGFN`^A?^1z= zS6zx#KadozGxbp~cW(4K@CH$Lj2ru-hvHF*ig&Wewsxc-RGJbY#dR%`?_$WbOZi6;C|aS0Bd_}s1YDtoSvE)Nk+M-(3(1gOsn&=HZ)T=A zuXl=AeqcnbZOu8s$$*I;$_VYQrSC+;q7Jw@i&`)8#pL*J) zEku8BCr_Zh#L`^fH7&J2m;z@{g<+km2=DGs*b6pSy|1v5j47Zg@`|zxw{!I@Te3d= z>7Xn(TxH~$og^B*K@o5Plr%XAm)V@XJxXq)ms51t*eoKF%`@ET!L3lc*G7kOK<`^p zXMB5pZ|?*1CECt|Z5`c8BpZD89GxP4v8*;eKC8D-@gbsDCE;PC$m$naE%CL^oI9l+ zXEHdskUAnept_UoX9TG9(22Q?b5D`q?&nyBn{~zy26|qlxKvto{fdE?KTtuQvOYTa3#msOzWTiR|5bSUVH^ft5xO?x1 zl&x3t!_{7+lNY2geY`o15aL; znCfa(eTyz6&ut3|>7_HeR`9{?IFoq|t;$s2s2N?S@l7{%#~N+tIlbLBK`YMh3!K0z z;J$oYEys71H!l+EMb|QFK5z47R5`DVQwTO!l&E8J0rpFJIa6x&VKVg7Oj8=x)KetK znH`@Z{e>@@nzCa!4smcW1(VsYueS=FYn?=nk=tSi)twSaa9*NlCS`||m8nA&xN=a3 zci%pv>IuL}B?vtI`rzZ2W0iBxzZCQdS|6r;$5ohotNikvdikBfCT?SYxtLpVDl+vL z(vvRGueq|IpfiQg$8h}G85)__-*Dd)<;mbUtDkMaV-H9r-0bV;kXgoMS73xN6 zhr@ncq)D$173sY>!wt%B zjSHFx;xGj{eW1U3C4O$VILk~NQ5+$MVUOXG^r5w=C-A!OVlK;{7KrSMq#p*NFOG2q zltPVT=|@h6{Qpu=HF0svuQu0)0c8Xe!iJ3&vGFvajhIK0xi06q*~hGh*A`?6ZSHT{ z5!+SxgqB}I-;N1x86H)xS+diUcH&wjN|5vCNQ3HNl;hABv8crE^R z_}CW#yfvwYm4N|=6ji79(Oae=WdV<2`oUtfs3{+jQOW460&d@!CzcMI^Agxmy>`(X zB?p4U6;cN#WY}|A`{=>0tNlp+?l@asS)>SEieB~C3f_OiL4ZY3ZBh5Bg@?$)*%EW+ zxX7APILC{$eI%o|3?Sn+NdcJw(Slq2kNQWak(dhiSgbwWJ!)B~S;PaU>dWW>1@XV$ zV~a^7PCyjC;j?V$^nL%5rILKYZoTQtcV?%6kziP&63r0~3qojw!Fv+!v1f^60x#Vp zMi!V8G+lb}0?(lMoc^)k!cp!EmNUNvLb#Mzg)2E+W%3BHccKL=(11|w1_}pe~lRK4i;!vAq zt0&~bpGw2Kl%a9SJ3z)7aG z4z@oEDhS-_uHcm*P%%f$#_)F^>_UHz@s|agTjxdQ2SN^-*~WIqkh92@XAV5{1!bkz zKE@?}yBLgye$Q15V)jpP+BV-)vTEc)_S#BnE)qaHV`#O2uNsq?s3iFc)Ntg%fy2Yk z)MO8;;_nt56%81^$-eA5qAO{Vw?~?9KKSb?*mx103{(q|^77du&6fAA6N>S*sGg=AQ7T&>?5Q7Q z96^8RN<4rWaxPY{Cwq!jdOU!f^QT&Y$ZO zHByLBx^I(6vi+NZ+x3TRbw6+4G6xj=Iz@-o7u6(x#D%#&GZ7P2bs=+r=JS(!0ip0v zv%2c4KpuuA74&Ce5hinHjnZo38^*jHWc6kw=ac?g5gg#6u=5L7Eh55)5KZlzBu96b z?s(XEmF}jAKmN;tnc21L!f(p6<#0)mr{5Ys%GNm_blv>G68pNC825BW`r1XUutIol z>d8?MShJS1CQZzH;Ol3iN4^Tf#h!Y;_G*g;xK+n|Wbx49xGDXpI%p=T>79G?T#qi* z)Or6RGW@idej6}NJoit&sc}1;A9+92m_Vvd`0~+?nsvVWKqu0PWO+;U)w>hpf9KF?Qvr}Gk8U09hvIoe)(e=SM*}`InC15qoelHeAyuL%?pBI-Hl$b0vtJBZO zJd^x1UGjK)1Z`k!vr7~(p(^!l*ESx%x|unK5PlkQvgw2o!&KGB4F>P1w<_<)LpN18 z&>pE+X2hfTsXfBPsY+Jsni(2BN0+gZ=vvi{B2-a_4Wn>3c>i3Vx!kQe>3MZy znGPeOh#c1srxDr?_DrOH!`8;D5M+F9LiiROVrL<{08dNO&*r;DV~;ML=QICWU+Q}9 z;(s9%zX;s?yb-3Q!BTWOKTJ_9Bmvi;Wc~u}Yv<qf{wyaSdTpES>9%`7+zX zQNmbL`}YiU5>Swf3DGtAis)^$a4t5e55T%@*Zb_T9u zyhX|SQ<+P+HDC5rpO@cSX@{Ua@n;a+{N%{Bf{-u!LY-0OI@#o(v+;M;1}~PfCb+5^ zThB&QMLP_E? zb0Qw^uNY}eqK|R#>uj;KLM~BzDLmij8kKZVmH{TLqaB-+y(fiy~3(-e)BjhHpG5JTCAaHj$q1J|t$6L{AM1p(PkaY5UeI6BG z%)8pCltRk#geU*l<-hkje6|HgBuh@v*RyvR$h+pRwhIXnsHIAoHkrJJrJjCZ%KXc( z8chmo4?Kf|_ZWPF`4UN(-yF#qY2M5vKC{7b8~iZ>;BT?-^U|)0?-wtO*z$NDn}wX- zaJ?<<=Ec;U%u1m(hoJ;S^Nc+E5$)lp5PF2J0%u1BcR#KEVU}N&u5A`?yBCral%c*3 zuhIn@ZDAVbr)r*(iaGnV9b+c$wBF?ITI~9JCsC;S6IV!R)fA=9YAeN*09yGYsW&_? zcbf|sE7a4~qU+{?iCYdo*6xbdYj*1Sy-tlSI5dGTuJI#kq1yp2_XW46s{U0Ksg#Mdq!;o)MyBP zbQq`XSS5`k$5DVbAp?`&rx=#k(cZSs*(FD1?azAmu(1B(&fmpt``r!$U`IR==oi#?b{mpha z=M(qZa${^%xh+CsuSI&e)>umQb6q0iV21ZNLw1SwpkD6uQ>kPN6~fr7JRyDvm6;=g zjCM$Q^=m}V_K1gJ=IFlIk&g;E6Npw6btJy^0CjvI?p%96NJ*&j<^l$ynBThZt}ZIa zpI4UKZu?uZKTWS#f{NtdyXL;(S+er{NY(J0x4c*(Mj&Sd2e zL7dlqEv`6JsH`N7CSgjT$7x;*p+dl3lacZ87IqCMtn!hf(N6YT+24tYW0w>=Z!1Up zwgYGwITG3FfsVG17LSOB@WE?<-~1f+Rj^F}!pF$Hj;vf+s2f9Ci{bvH?mWQ}oL(Qb ze02z^lsDJl6avzY^MF(1M45jEn(B=WfuCPjD%JT8dJUQLU-KI2s9!re{Mp?b%L&`B zZWtPWM8_7fHIMItLqU+mD|z}huk-VK;nQmqD!IIUVm-S00ZmZxT}7E3wh`xE;n+0zlE(;NaBT>DFKP?Cw4y<3MZjArq3_g-uq$yxAEPxEKbXS>Nh!#6b(=X;ai_yD|lazmP}Gmvt0)6ylF7wZ36=n z&&*%q@4XvhLRU_~%~;czXF9EF6PiyuTYT3mkAk5aa;jqdS5UY%l8M)lV=*OIrhXn* z4Aj}I6pnec&aH(m_;@oWt`mTRM$s+R)-l^d1UE{`R*2p)9fq97_inz3I+C}oC;T}r z0Ekam{!H|RRF0pbbdz(Jn^+)IaOl@IgnI=JPi$}Y5%qtBihsU=usCIX?O`6+RxRi4 zCig}UYq+zhk{gab-U9X((qIi>K1k@k_%(W!M;TSSDGKLl=+=s1G(h0M7GQr|jZvU~ zfYAVs4$$!ux?BOH0gMJP8o+1(GbL~=1oLEY-2kqmz*Qrd7lO2}nplLIM&JkdT0c1SBLNApr>qNJv0J0umCCkbr~)BqSgq0SO66NI*gY5)zP* zfP@4jBp@LH2?2}nplLIM&JkdT0c1SBLNApr>qNJv0J z0umCCkbr~)BqSgq0SO66NI*gY5)zP*fP@4jBp@LH2? z2}nplLIM&JkdT0c1SBLNApr>qNJv0J0umCCkbr~)BqSgq0SO66NI*gY5)zP*fQ01# zUP1yLq2QtaC*H18nw^eMxU)~cL357OdOM#gc+!S0s?fsAMh(xk0&VzAmO~0c4fn?5 zsGwHxf_c&*Xo+na(eZt`T3A`pgbsD8QPv6SR4;_e#D7~|;oy$z#m%zXC z4TQxhYv?b-L08wPIzb04U#gaHqw#UeYSU8#e^JnYvn)8v{>MMSoD+-&FdD#U0CP?- z=LB=k|9f*zkP(B77-YmCBL?$zFkc7rbueEC^K~#^2lI6>Ux$u_U|tC3gqNJv0J0umCCkbr~)BqSgq0SO66NI*gY5)zP* zfP@4jBp@LH2?2}nplLIM&JkdT0c1SBLNApr>qNJv0J z0umCCkbr~)BqSgq0SO66NI*gY5)zP*fP@4jBp@LH2? z2}nplLIM&JkdT0c1SBLNApr>qNJv0J0umCCkbr~)BqSgq0SO66NI*gY5)zP*fP@4j zB>#<&KxayL=zmtJkmb)?Sq&q3H98-6UhezXnb5VjnBFWR-nLrl)F6TYjh@MJNI|IK z^4S+Dn$U9+YF~YuX1A$cC>tnFMXud`AGG{Lu6U}_4MHVSFn(*TX}mQ{^EH0RM?Xr% z#wm+to$~hleW~_2e$;(G!}CSw|tHz~@KvzHdB+IIrTaJct_p`XE|CqB>T<4gSRXYt@uX(EBD zrP8;6FXQ?fQ7S4t(j=5hPS8fbrC*}3+h1N0?Kvo6^EjJ-yvFlOX`(pVV9kcNDk7=G z0T)7rra57z=g2c-VB$17} zc5Ou^vg74bXcfJLK#U&qj4w`d2oRMIVSUoUL>rBQnl%5GqWZ zjvR7bx8b&A{Q%uPpW-tngxLL}}9toci6x8iFK#=*g|bOWJWg zXd?w0(HnH|?_AFFFQ6J434bDjN(h~6@ndfqO&}B`H|MrX8{B_QRzK>W0m#2}fb^dh znPy(=c@n&O7g3ApWI0kN{YsiQL&|u|kgqfpBxFYlBJ)5~DM?*&XjvcxB zvC~LRFYUKsXg65Oa7aR^+_NzD6|mhxH^OUz&1yY zv+(q>Kh*~fdMB~;2GKvvOkAc-;ad(wB72~ky#NoG5J{*4E2md;ptvHm=F5;F&SNF1 zV&@wh2t2#nCZ>JML@))B?NFgV3sVqj-jVHLsji>6_$-FjuA6|<%GrmIGLqNi&<3y; z0UFskHxCpss!`B_>B)(jV={G|+p|ittmZ9ASyr{tV*HOM`w|c;VyttQlr^c&pR#-| z9QQg=^sGZ&T5hXY3G3BD%tOSm025llf4=*E{_WC})XD+l`0rRbGM2YmKSYQZ-n!rX zCOADB>ZI^0+)&Sfg;?)ydU#zoNrv3TrO)din+HyfzJ%8yCYw%Er<`p~@~!dx4>w1h zgICm$)oh-<21OZ$&C&}Lhrgu%$SNd)fkCFhDpdSUa0D%+w&5EGeChCOMT*gn-`_kf zgHYXWULQ_Jr()42!W!jar>0eRepeBZ@cjlhwU$;gpRgq$J#N4q7LB{+55~v*h#s?( zV1|C1pi7FI9e3LP^>y_}hBj1s2%OiI^iT(U*@p0J{N%f~NB?;LA$=yTmJR_|NZHdi zahQJ47HEDS;NBm7jXCcfAYv(RWO1O>6Z|X0WGDMp=M9T_uw8!{g=7-bN&7GlaE+Dg zrDgAh`knw#=b9eV9bX5nm@39v#Fa#jezR86vhP>arkZMl6(h)G||vK4=Z!V0>+#hqJf(%Ins!9%Bhi z)ZH7ND-;xLv#?-#fVIo3gdI6w@t1wxSKPmNsWY1& zLKbc^go-=6(a1h3SDCs0IeAO599ZF0*|*NAKhQvOFCgs{L4Ph-%iNUo>b>o2)r&V$38s4vsh=tE7sR4 zt$Aype^dhdso(bT^Ku+@72$g_fX#ckuC$>NV6HoLFHPs3Q$r7-!bRdagi8G7j}OGV z$ogW=25#doGLp~WJ`@cYJ}7=CdC$t5QA!OMAtq(WEarHrtHBr<8Ck=c;4Z5v+9{xB_#ao>+VExVVU^9$X!`x{=Vi6}`=$=m*8U6l|jIqZ)6);(fW;Zp?nOhd4{QTCj$bFyr0YDw=V9ORqB5aDO;E4fteW^V?MYZa7d3T@Cp;s51vz| z(>*jtu)fw1O;RZ&y~}rKCGq0UUVxkw+5TvjP45zJMA4AP65N+>?9gAj8suLn$zq@- z*8a-mYq-SL)BfZnpUbs{7)@~h6K6VIcz_6misuOZxR+xiKugX~`K+&!jd<*p$6a=h z_8~731ucQ0XuyQ6>w&5CNJFin*Kee^OeoG5CyCy}u2e2mTR?-Vb|n(|PJwaaYs8=xdG#ER2#LFcWR|0- z@pB&kA1iMiR%O?;jl-sqPU($EcXvrhcL~zn-3?L(-6bU@-QCh%(jd~^h}5??`Y88t zyuatWf5-Rwr`K>@=gc`XYi8D3`+KLQ9eWej3IJ&Pjo$yxG~J!ifEGX%tTd>)nIh>$ zGDX~~fhO1zDsTXT^xdV@K1n51zQR1)J95P|IMjdSLUu#WKSyk?%t7)8I~daY`SjB^ zzmgaQV%QP?x-IAf6jP5lXfhlO^_)E76bB}-KmCHF-}uEH*#{ERRY|n}+Ew=(mP=%v zxL={ZJ+wfb!99XE0LY=zE3_)W)yro~mOE+C3L)0NoJYL*M!Hx8|G8(oMH9#k7Fe|# z?s2EIZ6q@+nQQ|6r3AaEp}356Ll7h&YGWF?!H`g2^_il;i*iuJsC^`&;oR2s;r>=4 zKL1(+!GZcEa>C?a;#jPQ176Yy3>AL#-gZO&V205as%3pctX}%nuX8x(SuFsF|BAlf zddcDI7>2^JW=9B%e%CAX{p0HPVfHhp*(l@*5G0fd^$~X-!lBK|{RA}JrYJ;6Ki-eS z+KrHk17ja+`SZb$=9d8-qYNZgQY0N~mMm7Doe{E$6S#vLr?gT~5qEEpz>t3DgO73+ zHFI0jU-ftwcPUDzh`WDIc~m0RzE-|jfKnDJKm!2r`iuJd91ZI;_ju#D#bZ5`R#CIR zy{Sk5T6PAsXRV}xFtHcTEO$YpD<=B2Fx<;{u`(;N;1H?6HuFdeEz8gi5`!^`jX8uh zQuA8YSeNLke+bYf2o4@zg1}Bd40q+(xAzVBYbJp2iA~p}nwlYk=5g`6RE41PN)?E{ z_wlF)ZeGZP?|uM)Pu?GmtbaEv!#sSE-qrF!^!OQquP(k}g@Sa2hW}Iy4+v7DT}KZc z_90o8_^8dGxTyBrE*C6N3%lXzB{#O!UOl*mxo=@XU)~dBC}S7Q@+}O4K)f zQ5m4o(Z|MXUWWSDOkL&NC@-fO#Js!a7xmWFzFuA^IObRem8Nu7KYQ~seG>q5^3i+s zp5eery6N1FNQ(Mx9iEoci?B-!0;Xpo=n(OP`bc!?>Q%S({8#O$dd@6!_gDEb^Pw9(^dyb{E+fxLbO6X-jBOfKYpu~}M zvu^`fX180Zt9rHtfXJ@kZ})y~E@V*w*h@qp>USF)%8=;c(pZo>=)_ ztp`=5x-b@^Rce4Qo)-!b-EWP70JEaPz^Q}yNZYoj43>kl8r-5 z2*b%;h~{>T>iT7-tUdEtgCLx!K`N*kBmvv$SJ2UY*2BfwSqJC$C;N=JJ#r^%A!dQ>$4eb~S}vhQc!VT>Bx*?CI>u1>WO_m=mzLg8 z7x2IkL5CLE_5J)KVIf_M1HZ`VhXI`xkgJtqHeC*J5n9yx;rLI(v0sbLd*O@i+Qm02 zO~T{u1`L{p3@utvc|rzO2E1DF%GrPLHruBO>nrAr!LYXK63UbbE%LNfeke-75=*30 zBmqap%aDZEd7Qt;ytlWcti|g#nbM-I12L9xb{tsJUHl>kXRPx98k=1I6b%ltjBN2Io z7A=)Y|B*nUXsA_(N$&srC4pi7_+xeCb1uHnqAnWaKN3bP5=8W#gjK{p7fCg1J}!1? zlLv;q9XpuFm@dKZr5x~#WEC_o( zq!@;kgC(_#IorXti1843Qg(s=yy5@C)iKw6Z~Vr&M^QsW!re2un$N(*vJ0TK`QEY9 zY%nj$Eci9Iw-68yJ*Iw3K^{c;iXX>jP(R{Jb_fDW!~O2oYNaQ^HkoBD7c>B%0&D(L z-Tt5WsuEakLD`bS<)Pvy0*!$#YZB%uK`~}|kiVuS`QpIxS&Gp0c$a30WivXf?>l8J z-f11uv8`luN*g z5UQo{Z&!sIC?qv3+gi{cg%Uxg_N_e+FXlS52uQ^EQ1*gYF<9YrO6q26_^5Mh{xUCi z=SQWsbqLAd=Z@O4TdJ?z6DqJn2i^MVYvNl7wf}Z4*Rfq@FODrl@Istv&+?Ow(eZnv zKkq5Mw?RA6l1%g!`3EPaZ;(tym58t$w-NHp-^h*o?|iLnAiui%fL!#$=*FQR03_@o zYp5O)y%e~Zjh{7bXNB~`2#2;UtrImj+(w7lhyBgTU^C#0;^GWx5#vd&n2ci)^fzHh z6q9zL;?Ju7!pEVn06?)ybUauppvV!9ZZZjp6zqi&&*BqZC8_(1D*D?+`mkS|>{OHp zaVxVi%{SVhzg!H85!uS^ilI{1VGj-TglU#~P6{=mHe~VogROjv44ECw{Y%rupCh7u zeI#}5mlL_i)iX+@fN-ybcLB*0EHNv#CH}_i)dL|?`i8uG3gxfxrb1Ugj{W8&4+{-! zi-$R)E(e_ixmw%`?dL1uZ5r2y!tG({<^Vxgcw_9PYLrkCxu5``_xs9w)A+F zZB$75RpaRL zWA#RUadMsRT&_(`=tb8W{93}mh}P^AR8kZ$`{LsAMQ4Z8z5yvz5i6P{Pi&ID%oG5F z^I6JwxIF!|4fW296WuQ3u>mO*Qha%WOb+38WDT)15@GCrc@pEu zn_+yHhSpAmflTs#-~`~Va_XW^EEPdD;yPYL0;}y)XF}8iANGuzhh6rBp1Xn+02eS8 zce$&3urhRhMY$kuR+>FmpiAOl=c%%Z`7_MPk`$_PE6uha3A(Aj%2qHhj~g9Tc{K<- zPBd2cj6Hiz1~KI~C!@$-U|T$J5}?p0*vWJOMP4o|rti-KBlA5CFSH2L)Z@R#3C7>& z|D6-b8w;gIgQ{LoLP>k6=bg;IJtv>__Yr7g@4jhD0^O> z*)CSJaiT9x)x%v&WT;%5kZnJ?gtI*e02)HLU8trsr|?4UEEXM{&VWqQvG-*JEBHDLLJ_&_+)`)%@c0Py7##8JN+8dxoZ z;#$`%n$9e-)Qd_2SP`O;i7ChfW!d9li82vVfSndZvc;7_-*AN` zipZRMLS+jaeo0jq&ghpR@zBpC{r^Mq%F^8DCcc$q5VBS7G%*S``e>g$2rAo@zyH@j zY5Dv7zmu{sR-mmK$Q^4h|NbzN-LgX0-HvS#cVqgg@WS3)k%Z?zNg3jKxA1j3?Z+Xf zSu16{Kf%R0gGg4aJ>m^MA|!VQyDtDptMF~bqDoUXGU(LtW$h~Bu}*j7dkZi4ck~*f z5aNv8ze&lqgzRd3zi<>Od7NN?_gP#~n4j0`ZGO*EzXMh!l4vRb2xWC3>+ASjZhczZ z@!Y=enYIdcz_&rV%Y>>U>V7wb#$TjVLQo?dOcL?VmizVMk70D2@_aguCF0bVOO2aP zkYRx!g+dU(c!6TJpd(#-GTp{8g{Ky$Bbon}foV^Jbr6R{zMT~6Hga=q{P0%rGxDv= z=GNGq!z3N`m-Eif-ccC%1fz)C-=v^)VuOwG5Gjb$>Wt`tt2XCyM)O5nVr!=RJ`ni`~`F|w^PR5Dl$?$szM~%K^tFH^^{-EA(Q?S{qUTU%?YcVqGOhG?fxJK6y`zPh2%J;iei#-=HU>HiW_ z(#yzq3zQAGftPG-CW&Yk)%{a~-=?bDX5rHWvgvp7{+h|jEJaCKIGGrS*k9yumaHhS$9f4y@vCQCZxkh!T6`~XI0-eDeDM+g)fNSbjEtIKmR49 zPgSZpn(q?t)#6}A_0*DVVrVtsqg1U&QrpgB=(x#W{Ds6JlN9XxbYAAdW1S5;k|vwz zemj>6A(0h(6q$r(@HHm@C?Pji%-Z9;g}djgo|aJ{mD^T>!dQ8=KiKwGo|tv0@n1qJ z))fy*nTjED`#~AcrsXgj@}wDJP)^7#y7?0~%`*q8GB?(m`hSzvw-hcbe;U>vrdB>23)fjkK6JiOcUmHiGw<4ulnu|rud@=vMNto z4JcwN8rB^miayNT-2i~Rc~_OEhD`G8{z5i46Il!Y5)=pv}!{CMwgWw5wf&l-PLIaMzhI;L71mbx2qt@E<0`5BTmESa{>;yJPzXmKSDlH&w zs}CyIB-TAol&59?d6W5`0-v}r~ ze>4OeH>H|&+Q9o^&r(WpHCk7cI*=O1M$w|po{V>Te|9ni06yzS{9!3$^sQV!qYn^P z!rSSHDNoBqP%fSA15XbtTn`!+Y9pbXg%~@(f{G%{Wj@Cw;!QYP9xW;*{N5MA`^j&2 z4y@sAz@hB8ePNgNde|32+50lMxT`L}n;T~D&0a>Qc??2WkZ!*+wP54Q*)44$ z2_W=+2T7c71l@C(n?Omz&*mITR@pOT#^sgXg?w?Aa8-1h? zCTFFaE;dDmvW--vTqzZ2IBt*taM188fFS)|JYxJW)J_(p0*is7=WEl`?)%I^6^?qn zJf+x!Sv*eOWB{OhZnfd^O(Q%pSt(CKfr5I}`x0E!`qMh(%^Mp%dy-xdb2A-Csbj(% z<^$YoidQdO$SOQ!+Lp{ssB`$KU!otrWd@r`pc5F$xvax`5IkEP;1^Y5-5xS_!@tRM zImuFNLB}I#_7_v1%UdPdqA^rG7sL7&b-k>Ij$c%7*F}LltfB$|m%VlXpk<(dmX_c7 zfL3mg+6KE2!41L_e$w2$KIi%MTB%Yd@b2=z!Su}Te8!rQ{3#Bc2N4cIwVs<{kcgp5 z($Jz`{ZBRiAsYas)-sqV|59SGJx{C zalr};7T%};{pwHK7lkg%+YEoXYl<{T%1p1HRQ=GOdSGzSIlUgIEG?QYekY6nw1)$B z6ae&^e%dgk+FQU**DqQdyaUVyd1y}rbs=q2l?aFy;H1s*nBm9Z^OmNw*9t!i}ZV{wUrZZv@ zU~4Vup_+pFO_3Bn-GcV$ZD9gqs6rAEMPyE27}g%EGvsiKUMGtfXy_X=e|t{M48o5c zD(v<%j4p#L{SdHq$4>|U$A{YRPB%x#vg9D*w_en99so+|YWV?1xch121}r$gJabR6TmDEzLo54)9a(as>%H%gUMd zf}0YGI$_(uSWSo}S*OJ?B3VXME!2`>`V|l?^!8!EY8u>PN>AHJx=b@G^BPy0D*7|N zVQc8|!I%X*3eDt2{bzx8sFve7c~ZBx~C zUIDpJ3fXUU1i~K-P{6kUhV@{8LzeY70+C!q0z$7*1vPf}Nk5AkfkpCAkhsm43k) zHUtmEe;MHUNFpq!=Fls#Pjz+8J(+sY9^K`7v|^D|qE_+W`xbKOFI&s%_yDfWR0u#-vmFtFQ7ELVZ z6%31N;GPbzY34Yn-TsOpsQ>{%v47kjFc6KLtsg6kAK;GjU;u@XzdQU#iyZI2408rT z#=1nQMATRGX4FK67L8gxjOiatlzWhA$pnLp*F9}ABcq46)-DF)^#8*E;A#FZ185Ff z#(%(Gf?P$|qd1I`$j59;-gw=eiBr{t>pT;h-vsKb4qpg2lA`hUi0m#>5gD(kj9gBh zz1ts}D`dixN@S`0-!cH22OQV|9@P7v4e(gv|Iq+{LjdQ%F}A;P-C|vszUiifb0GUr zBPCb1Pj;#7s~4sd`7vmoty&9pqi(~!r-Lff6D;> zs~%uqj^(jmorY*?7)>u;;_&Hp%nkX+g+z}$c?~_gbv83_2>f2nz7rrE1agW~rO6*_ zeNPLsm43?c)xsBfivfaEP(1;q0aUR?Y5Q`RFnxDGk;cx~e%H${tDt6>YTl6t$3!AI z+;|CE%|tHZAV(K5^r|z@$1+c`^As1kwNCjPN1lf`go;DQCW3wZSBeIG7{>z3*4oA# z%r@xXfBqX#_$vY)JV5@5ay^PA_r&qYeI(7(y*n~a5oAyZRH#1u2{;Gf7DF3A0;EKO zW&93WXEt|}4%J@$4FSNa$JvL9y(!`%*CPosR?lx~ZR0j_T4jCL>!Ca^3xyioYKEQmgJ8lIX-++=RV^xY>{7S`S{%%lSleK$gc*1lPMRc3FuHhrK#0=;- z-~ByH40iU@cRYq`%AZbF$To8iUmlOxn}-9x8$-co?z@bGRr}3JQ8@-Wb1V9gRp0FE zCJJ4wu^W}AD8lAb2`Nm6RQfN5W9c#9GG42%f?fkOEZcVLs6-3MSX zj$AsV2Zml218#cwV|78N_mmL94acv*{EC_E;@{Og`9*0OuYWtsQTsXn+N0Bz|F^^o zPGIkTjbf|15Bb+$$zl+}GYqFaH5>&7Oth$dIBioM*aS%hI@NLNEX;vRf&KTmWp_+$3OsDe7WJ$ zdpN0oJdeiO%>5L-bZ8Yhtas&TPEUuTo9XsCADnudR8R(5J$Kr=m)TR+sxX<{J?#8x z-kpx)De=$kAYOl+5Qnvu^r+O_J()x?iiW_a95b{a85;jsU^~YZkl5G>j!QsW$J_(O zU>{IM^9|yh*mcm@D{~5cF{jP;jJhebXn=y=<3B#22>;}KQfYa6Z_@iHGEKMSIdL!C^uJe$8($Pre`Es;b20c6#p& z)kCPdRe^zhigU@zc+Xp!*djNr4(iKlo%|(qFHmU7upRcgb*(kMlDB(`i%d!=fR|_( zTKS;JqgyPSH4{6Z)joXfjg|OGHAa7>w?6}Rnoxic9UtJQ_MjnS8Zr3052$X^ib<55vPJS7cYvPy4 z($CG3 z|4A;=>7sEz=aXk}Z{}X0oJwV8Uu?5G-5rtFiwUqPf2|)dG_%BlW}DrHuv(G zArO3#u=dJv9xU&8aWu^b8gSZ2rFHvVABmPCh1l1;^xwQjeYI&Y-;^M6T?DJZFat7y zA=Knsq@IJ_An0=>uS2@!XOdchWBsVIqDEPoF;fs3;9RZ7ZGWf77Oyhv%jufkW`Ql6 zlcS`N=^(RT{^q%5#OLF`kbq(m^jFN*#DmYps*t|>Vd%XLy3kg{w zGLVM(C`>9LMW=$*cB>aC*D7S}V!T-DT^DvsD&|4M1*ndJ5$YRR$bu#)xle0z={Qu- zXzU$`X#xIX<@p4_xO*_9I;{Y5VW8%1HMAE#L?vU4M^UO9QW~Ij80xhFr?qX zUgdy2spVD`EIw~#I%bA5HpT)38tGaZN}K55+XW}FojQI9MY4b;}BJgy0S6ZS)Sgk zrRV^RGn{r?_c78lKuf<`_;U}Iz-u6%X;hE>oBmH0+{filahIL6y49-FX7AsC?DWNV zNxcDRRuhcQuU%MpRFga^C~KW>PswVvfT$n#)!<>7&3GE|Q0IWt%TY!{m_V{#QNj6G zb}IB&@l3qZnb4x{N5{-?smVGvpVrb^lPNJ7YdISl&d8;O7Sa9ZmWG2oe3n=p+3n6V zBHxTe4M2apWREVZ?lWGOjRY;!FApv3FWM1TYVM+B$-Y{K^nr7(cG#v2dl8J#v+PtTQ7t`Hopg)>HO`$HPa2?I`)yZIO4nPVCc9Araq*$ zg%%-&{@UvT?Zd;hHwO4CCVt6Ne3wiKRI!TQ-AfJu^8yZWNYpgv)rq^fShW;l;}F`6 z4_abjYnP-laFdaz6NMTayiJga&mzW(EKiW>t4hNz8ECMU*kocZ&#HbKiW}!| z=>!11EnpT}cGJw1hLQVFZsU!QH|h%<`BxO(VY)=;?XBfN>gjuH+r?9drB5(J^^58M z6i9a)@fnM5A-JIWAf`>LZ}FgBhKvdLvk%#)ZLe1jCsL+5?;Yj}%*Had`&lMUXp#MR z+QaTCnBmk`94);PRkmE(mju=ybxXf!}00Zw>j4K+Hl%)>RTTF zT{H~;t&^u#<$ce;#016h%VQp_u5h2yLe_l?De3vW9&_e2C7HkJ_r(OTxri+YH=jD- zsCePqZvsDRX7)d+TiI%bn}|xI|CHDF^o*C_Vf6r$^W^nBLaZBsL5a^XMJZ|{bbL4O>V+3< z<6P88&H>6ysbuQTuw#b4Pf4}(elOP<2Abj0NRH76({?OHQ#x^NS8?97HCdsYmrOg; zzGx+-_tyZqUvYV@2jbfL*N{6SHn=0r1ctO8xg^;{@v*5&iyi_^l?V5$`T`!){&K%C zIp*9cB?tTfELJ$R{fQ~m4N1@$RKE{e4-%m0;=6h`k1ZK(I7Sif5&4Y2LlPJI&EM#9 zTq_>s^km*T<_#3CcWvRO(u$Bes2UNpzg-XNoTYFBzvZ`n^=`Cn#N2q@5iOVWhs1Bz z^r&!)&!wN{@r~cH84zM&0Dx4N1cF{GZ)_8`?-<@SNr}XX<~nTIQ9ezAU8(Q!js@NE z0))f$Aw^eL8Yn@yxvW=xK{&<0ZJvc?4!{thGnQ|=J$TUX+Q{Ugd;y_PPW#?}{~-o$ zBEL|zhbXMtjfgZPMCv8tU;ft0zowA(qY-h<(mzqlfp4cVNPNWKs88$r1yg^s;oSFO zk;sZ~m#<#?UnFqsN!?)6##lQ%1lPM?JxG~Kg9Wz&7yl8(4^27p8l^;~Lfr1yIyS5f zp^T2XNCnWbz-c}9aqC7}7C}}#kFA6~@>g(p%tPodsAH<0qLv!@pbFf(>EP*Xy4h8o zb3N1&5goEtJ^ZXVwE)s$nr(?nOb16h0UiAL20gcA#oXupo~~yJ!|<45ygAwH83o1) z)oF|m`v2#O9!UQStXCWiDG)CM2r*SdbYYjaSCY#@;NCU;(Z7~E@2;%Xr!;ff$jJhF z$TjSx{-fjq^>(jt40udKC@ELFb8}J!8uqmP@EQJUwC0Da#Ju98`$9i5vS_A+n^0TIh_C!_P_`p%8A6kgU2`hPc;Rj-- zH4Ke{H;Yj!h+Y{{!Hr=Njn;Ct5t{aJY1S-`l-(1#WGlx?Vb#Q(igd7gVoI>Ok-ppT zJASI)wW!`ymHT%oJKNb@QXuVT*63iO{e{F0fdsFd&Vl-7weFSnPafgTPlZyPDcz|z z@nk9Mur#0pXjZuiOp((UNNZO4%a%BaT}sW&kS;7?SC@Beblb6H`Q^8>Y_$u~^>Q+(KYA_Bh;o zI#b_o{$fu-R(ks|BlW$rzXwJbgmjy(VlQ!C44z8E=c5g3EToOGK@b-R00xc)2>4pL7Yt1eN;RHc9H;VcC|JHQS!+Aw+;716*uq!NK{E z4V?^w;mmZ)dumd8HCKW~*X`gEK||uUs=pT@E9T?f*EV~HZDnrKZA2@WL&uapg8_y zSvY=kvYyrB-stHwx>Nv=Df)}a4Cl~C#pgR39p8veXtIIJ%Sl-BoE&QbI5mWJAUEk; zmDpk~wVnQIdsMSaJ<-24Wz11+c+`l$Ad<96|&Aj~NH9|h$H&8civkDp=l^S`=R zzb5riea{nGbax5-BaygvO)1?Q2RtOa%GZi`U5Hkg392%{kHP)~d7UE=i(Ta;d0pZ? zlAOLPFb$j16nB@m{5(VcuFcwM5G?pB4s@PeMi^FcOyn^$)6LYdU(w*1?87b!`&?`@9mZ4?0_U$(Q!;_ zXdOA;>{~G))I2CCMB@Di{s$p!>|nbg=C#T6%yQHqlfF}Wme3;C@V{qh>y+>s6EQ`5 zW;j;+wm6=f&%L!^c%)#O55X796{#N$ci-PYLX;>dMA7cdk~K81_dV0dA5lQu{p z@|jfcm4j3A>9tIdM#g61I=A$x5w^8oYFZ`1yklA=2awomz&i&-B|+nQdO2I+Fl9+= zRjqqVjNidW?jV`^9)cLGOnwr}!###lEb_7pZwETCYzn|f*BiO6AXdzuGa^tbR(uZ2#R6NV8*6=Oo zw7~oF=7^0zzU!rlw-FuDEQRNT1<%Io5E*at21O&e`_>wbP*9GF@qh@Y(hAj-Yy zNb@e*9gF+9GPj22PYnV6k19+RRdWPfJ6vr0 zT0qNod%%fsqVR8k^+TA3ZT!=zeNbUTp9*!0l5Oy0tz`>*-+18zx&ZT70)~Gk>f`pi zF(sDDbZUD#yL97SoRs${Xj1B0xY*FS;c{P8=M%bHfu?8{l#I;olo zr8d%okwDZxN+Ni4vy6YQ1T*?D1uX@mtG z?C?(s*eK&XD6~ttC{4NIEH4Hn_zb`Z$QOVAHUOybAi_yH$~LiSL4FzjSz9|#x3mNL zDrDi2HC9N>@i4vTv0sFshR)y)0DW7_-u?AvsOQ`ApRMKT&?9l#tkC0oCN0W0h8-sR zakl4~wcg5l`!ONXs1vn6zu?u*w{f zf|iW$C}Fp6=O5Zq0kZUGw0i=PrELs5jL#u^8Z=?)m%8YOnA|LjYiGZWg5C62jx?TV z{Jw&$R>FPaBg|(68URj+kI7)Y?cvG_k*t3b3aWJ>1A)(d<;-73<(f)^=s<|+#WUeJSQt=SL{%a>AR)m zw(q9S$NU&t1ZnXwJbZ}yr^!}Vb1N5$se3%`$>QGZ?O)+V|M~%>Vkma-U{D@9I$hNy zMgFxg$sN{{e*c4bN+mnWE;WvJu!6rzD~<(>m$2%a;#!J%Mn>!~&dll%8B7|vHd57s zkPS?yaG-Sw8kST%&K_lquR9Q)Sn?eIK*J>ci=dBGe&oz60I1c`Q-y8$DG~NvJLYITcRTB0Izv zPRtfsG-6Tlr zDxm&+6*lkN&a`6En=Rw8wHd!uI$dj-GjAg5?(99_l3`uYxV zqFsR#$jN?!{=AJk%yu5Tp4w3v)M}e097#hv?p&;`iH?|24WNeDtgU};kR*{$+%!v7@DUlG*M&ky{;(X{A}%^)U(EJV)H z6qZ9#UjLSmW@Ej!^-cb ztZ-@4It9@bWBTY9IKs=vkUaU&;9BqM>H!KP6k%T+UQ#KCu<#UN?Q!D08RUa0c~~2y zRu1TXO9ALFEhc{yOT+_M0V@*7Q>eMx3 zDOKO(P41;Ua*#{IsZa8__CWHDJ&h8Px26lC(U&|2y? z-b;RZ!bA?n^ou&n)B@RQ%knFol`2iS8`UWM`y`)!V%ZdVS5KsY{9MrXiY2OF^)cG* zOn`D^Juw0b+W?p1rCRpK!dWGtxsG1n1&BJ&NIp|+Nm|+cH5Pq-^0ZWI?!LoOH=K~o z&`N_1Xht?LB*0|n-&Jr|3C^b_z=eX$-P(>uiPB*7S`2hUM)%j9-iOpChW&B3JHbI} z$R>Ad%{zwshh`aQodn~4sO=ZVpjOK-O=es191~(>GJ~w=CV>KxOUt&T45cNesinINv*_ZDX43AFi z>EhYX9shLsDa|iwi!EkXZ_YHgRW+po_l}6V76?`D&%u$CCrLcB35nK zJ`fM>T)<|MYUzzw{Gcx>dcdwUJ4~`+MwL1?iKa){g9YA>fnohh-T!lKkMI3(bR2w{ z?!VXevAKv}y#(jQs?-NB%iv?*n;vpXoS|822Y8#j zxXA?%^0@<|9$+lQjEGK(9wE$k-@&Ju9VhE#Ul~2VN@OlE-c5kjgP-@(_@iDn`OK*Q zlrK+8^!3#u&AbRn&TG)g@y8N(FNT#m>5dm8_&v`EKi-oYx$A+7?#B`-=wr{d(W_($ zG~r2vL?d@e=nO%(@E=Q1KsMjxNF{7%l=yCuj99 z>N>wA2eeW7_#K5U&kV57_!Kqx^~UdgFOQ9#pbJ2^wxf~$R2x7sx#>KQ8k7ALSuWl2 zmt2zOK4PFd_K&nh<#EwKeXlvj`I>AwpMR-~mZClAk+V)F1wIQl3~>QOCYpX|e(84f z5x;_*Qg$zvEO3U@-Sffge`l?`fn(dl@&ytd^}Tr4gt|d5-m-AzV+e%ZXy~&%%sOq* z6%%6%O48~~jnJh#p6KVDHT4S#)+J<~;#jUaSg?s=x*)bR>9f*QSAC@!mt^Tqd(`<| zv61mzGr|{xBd7zRuKIsXgQ8u8|FC7TPP2UE4p01eGBS<@YS{laU@&3*h@v;v)2wk zr@^-Tt>K#|%ZjCCjFh_5oOgQWW4RTw;aYy;5!fcfNUUKRTK&|mGd%cYv(znxHR7{>xtN*t6AWAO)xa5Sra%7w7=lzAdm z9VN|EA^v2b1MTIYVLtmk(y(&a zcD1D6bDQ2dRuzfrn^I}iX3kq#*8v^2Cys@hv`wI2V)=L?x>aZ0`2${Lj`-|Uiz6>8 z|22}=0di7yphCvkIY>h+vL=YB7r=A<*E^Ji-BbueopRw%?>3#SJ*?XHwpD(eEGc&R zJMDQ96?0O^7!_FpMCHUUO|XbLGwKU1stXPTtMH)VzGbfB^6@=wm;O{0RAI*Lx6}cp zM;cx`q>P)>)O(WL9Jf|#ScR(zu*Yw_>007k7K)zwrj`K*dd+VRJAbV2_U%B6_7NM4 zCYHOlqs$|3H>-Kqmc^r(*w`wkgHA*9Et`Kh5p!eir|wQvelbGw{+d5=fiF1cXe-9! zbEY+D(YW+Uyg8;J%E zj@0vWIOk6#n&6L(t^v2VQ!tm*4V` zJ(C+8Kx4DV@7U;1knG8!F@>LOh_d#x6Xy$q`S4E(JHzY9lT{i} zPJE1Ggx}W~7j3?4VOtJ>QECn)p@E$O`Zc>p_K+ikZ~0{HG{%D9h#VKBj$a{B4myYe z#_+(W25*}Qw#NXPL*PtDaR7RDcfn%&re{ezMtavfVbn2f(`xMkj+6ViFOhssb-+Qm8kXjlN~FwbFTXQ6WMLbP--XMyhm)^kjN~7XS1Sw@K}qFLSTRod`<*OWzK)8 zlW^g9C(+D$E5?FUk(Tk`^FEYfw09Nh3g-OP5K0ZNGBDC` zjVp&kj|EFQqVlPvzk2JGa9oFbA>G|gr8M|8zhiQqm^VDVj1hm+4>>Cffz{m47R4}s z0c#mbU{WLT2>O56U}qknPAlP@L|B+dANF}g!(HtMoc{%`p3qlQ0**sQAUnPLwJW(I zP_I;swrZ~XQ*T^p1%}`aWK!5OoAIM)(%IS?_j^#wud6M5RM$9@#0OPp>h|)J2aOE9 z-5sPSo&QemnE3)01gIP|p&uLPuyRCC%A|I_)>+oORIRNXG~yC$KwQ->Qb+wkWLKic z`UTgz?HZ$5z0HGFGk#^Ft#=n)NyyCpn>^$u7&D;i>975FaOA%xt1_i7lh9nH`2w>= zxXHgeHc+l*64g69%R{Xq%eznaZifvpbN_k@q`Lf_Rz_rHZDRDxctJs# zv+>=Otm^y%_E_UM#pQLrhnsf4EwBcsi7gpEO5-40x>ZC(qe0$d??W#e$0b7}|FpGj z+7`3|kBpD!`Di1<%Ir$7sxbZ7$pS=mQj&N0CwS6Ek-EZyEKvKpbtXuJ05R|eSFT}G zAh3k0aApfb$I?yS=Z(U(oWBDlw_S1+wkfv}))nfc|04eMvrU$qIHEclD=u-`+@B8? z$P5KL@UKYtp9^BS>4&R^;DUHaZ()Qc0K2=6D1Bg0P5e8`OPGv_PmdKS}I4Bv-!@^_^4Cgj1@443+MM;eN{SHLHh1omJ7OnQu| zDh>#~2-ai)fc7)*2F#|-xj1Lo%_%tQ*q^;<#W)Oem%uc#B(~Ir1LyWqS0?rv@9myz zizk`ghbm1mWArO#o;KO_39^XLA&ORD{R)1vj1ZShl%(SDc_T46LelW&xETQFhETJ| zXX#YY{K1j(o0GvMh0q>B6F=h6B^@_Ku zfJ_V=fz6i-p*Ncgp-J4YStmBLj{%YbAWrI!mSY-UG)gyp$Jpa|Yi{cWnH9W&ykx8x zmUorRs=FbChV}mESOTP2IAZ~SzX8i>yC{CI+oXgR0WI~?sTuer)jf0@%q$q!L;l)J zz07-!sQSj*5~AVi&MJxl2wM?aq_X^XgwsI5v;zexrY7#=K>XS3=Z^7MY0zDY#}co7ub47CH%;W@Sgf|>r15UkW3?Z7_I(N910{QL z!}Hz)S4TrX^?6sp%Pb0KH2B_;OFxT{Ch&gHZ)QK*o|1dfBp1TiV0yoaS8Wl7cQHe3 z=FsylkJ%b$E++s06+JB-LgS(q-e%AtyFqT{hKmrn-KH7Lz{vghYPx>f2V&M8KtmCC zZkM2g0PG3wie%BAceq`p8rMQlF%AY>2_Pgo@ux1?7b8ZIrls1~!@$>iPhW24 ziT9Q~w5V4%0c^$xJL+>0pc+zBEh3%M4Qj1y?^Q$!P(RWzeEiEhp*>%!jyOt2?W`Dx zTDl0Sp3fJ?tjD(KuTCjT!C(5VVOX!sr?H!s{$9glrNT=7J=2u1urpvqPD3k ze*jP)`y${DqYajE7g|8$M;loM4eHqIr3B?tw_UpE%dg-gumAf4%Owh-E2f~$zP|MI zHmQ(MqOOHGPFX&7ac)IoKbGCdKZf}4154;$j1PcP(}SVrxt$+u4B73FX>pd7cup&e z3Mj~y+{amumX=aTk+zt(CY%rjv-rX#*dH?f4{L83SJfAFjl!Y3L%O>gX$7Q_P5~+D zlJ4$q5Tr|KB&16aX#_+-x;q7wxceM@{_x?w_kBLx&wKy&9%Ih2YOcBFvL%)arXYDv z_`Wr4>dSp1NL2P~#?psbqmpgDiCUg{#L#K9WZS&#wwByg0r8<+Z2;HNxH2O2zYMd0k z5HCY+mHELv%RlFIil(DFwLM2MBg^8cNl)pxHhU+*2w({R<|4yzo zoM4|fJqQ$hvrqLQX@+I=^u_gfV8Rm`tO!7Ur6gMVCUxI6km8Ru6vUeRNPu(3>gA=hwUGxps7qm za=wdxft0JSow3!F*U;re3{njN;)#aJO;58^)#*GbMKa=He>>9LtQAG~l1}S?A>UU@VPkI}4e=NP-PL11oN{Xpt ziz}W^X=a6&&sskOrSyqZvjx<1YFsvw@vE~1eA01T;2*V8hEwb((jp{D#D|}5QLbbr zJlk(+%Xj*)`+d{R8nac-9*MI@TEx*ZPrw2+>RZ5U>w66Z z)!x{C`jLI}ge-QG1bf8>$y^!hr%y+hRKnOBv+vhoS-Y zQjW;^+hpf3W22nvX@#m_AwY|xL3UHY<0B|EVrsm)wyqlgDvWP<1Z6)g=F@-UBeXj7 zl6^aQ@-quk40-9d&DXO?rj)lk*yc=#3*T1KVXwh<0#}B+pCtXxQ`VR0FjinsJ%%Ph z8N$_HgmDtYwP2vnrYz6P7~2MpkL+ZHeOQcyt$sZxLrHbERktTMwaj-XoKKYOKv}dX;sm%K5%wiFyRSvHB+?7vi73o+O(iD>dNrGW(?>n7eq2(i++~#7uD9w*A2LGTvcZ~qntUEY!Abv zzkd#%wV56F97GqoaXaA77ORIn3#%dveKdQA7L@q)Gelt^wogFQ95a-A%pyyEENB@xcN~ig$*5KE5|0gkKxacZB zV1mV)zI#SFNcvzy1Y;Ud3Qn^rK8ZDfzj!BOmmL}PlVP`{r0Bc ziX%i&Z(#S=F#9gg__4pE>rjXup)|2+JXrAe@YyO#zW(+#9u%~}w+VE)wiGW#s*8~i zhThXr_(C>|wgAjtG@rb*RicI#l}!)`t*K`XM||QsxROpawwIyCBw#oM=pFqHvC!^^ zKQ7S;C;6mjTsoh0SMRsomM# zr&v@&U>$pkXZYXzr5?9N=jT9}#rsxTt z%70*WRAEbBS020d>Ziy{n0oehfM2{PXuJ-gCRusC7O~?)h5!@ zEejRic@cIP+aVM**%TJU9>*u%P6Q!Hi}}!HHI4Jsy~;Ru^Kaj2q-6=tFC$UAa7n2;9seR&5&Ax!rE zsaT@K-5#TNbI;RWI_RYs4n&zBU+wwz!w4fNfJA5*>9n_=SEdY$?@plFEQ1r9T{vnx zWF-}g_!tq^MbVC;AOv4VbU>EBU=LO{&{Qv$aemr^UX%18pGc*u-;)KRv&nQ|1`vrJ z_PgHTgw;$ZEroW(O@9mtp8ja|KoFIOhi)`1avb?|&kdAVCccC=VJ5Ih!@RbA(%8H* zrf3ZQ%)JNQ?29k+;bXAYvs7=(;4}+qV`hly;MuW8EsCtnZ-scw|}=ARF+wU6|ZmA z*P`;l1d*gh6!0m(C*td|JmdW6kKs&7Ew^)*KRc$>h|kkxjtjQ=zp?q`09tCT#X!Qi z$HXL;K#EbKVIe5zP|e3m3W2$(5DE&YkNmmdM=@GoPPS+k(Unw@olQ!DbX_#N3>_(U zGfGA!ux$oPpYFd~;#6cKyXAdJ-XqQuIulHOx6@;<7b?E1Nq|HJ#zZlIB+E?Y7<~ol`O94kKfcl*?JXHGe3c_1ZXJ z*Dt(f5CxamC_yb!P;E$!PQl%!hzG~yDGPh^kW;W4m&cs~GAm%G%GLvQY!5xF$lb%% z+|)fODewLbA-Stjb~ki!(yb}T-62MCL=GOVD4g55bREB;UcB5FT`t5dwHP?>UwWpU zg}MjMPP%s;(}?kzc0izSvINve=U)g-czBk(ax`H=hLPmKke_<1K)P;A6 zbfpX+m0^CWnc!6z8%{rybjYOsW$PhV1vORBC2b=1)HY<&;P(tzP5)dMcEC07^VW_N z4ttKe?BpxP;I#c1Pd@bWzIC1|^&u;eFogh@GilL6|MJ{7iJe`fEwyoe1ryqB+tyuS zL6PilQVM_xu{?n^6?)Vrw5-H`j8~_RSONB=%O>*HC!%x*MuZ&+?7Cgl-9@puQxl(N z3T={}NW8taR;*BCG}f=BZ;BTf$gLilur7l!=Wt>`pVkOwUqE%~+e=U`sjN6GnuE_m zJh!3>P&R|gzOfYd>B0L#vzF6T*{v792V^^%u_x| z1n;AqV$5^k(6rIAa9P0JWf*|us|bcf{wq)D-TP4qcwS>N=(88sb{+21_@}fTZk1u2 z?wf~*kCrQyL6hgJ&3fvEK{K_~F(S56?Q%X=3)CT#n7*T1?h}b1idO|fC#<>t4M*QU zYVP@!(HgD8+Eds{Xs z@|e!ucwUC3-xSP47bCz8vM#1@g<%=w_BQ~Yv+(rjAC8G2Uv7x`D*1E6ofXR@n4RodN#T%l^XL||L zh!Y%9+R)WEXA20WXJi1TyFVw5h*x$xdimaO$DeF8^{t2a>yA_WGXJ4+f&Mdk9c(AK zO)*Vd2E}+)7@b=|yl3-%d1U$FjZ@?^wgeSEdQ?7-?9?8PerlTM9M^!t4<*vkI1^+< zs$HpmjtJv4=-9yGiw0zobz#MQcgpU zZpj3rNgNK{(_=&-MSM>p&Zr_>dVRc;GC`YKlHA#u61k z<-=-shV}Vk3Jq+Y@&!Dc(EP0If$$DnxYG(fpjI(qcLa4c- zhN|XffJ(wT)3a+f`m8+}oda9>q*_wm)0!)uP8JIYm=eXp96TEULs#y>n)36KB1FR+ z3&!58yN)mAeV!{I5wh_N91PEyS#a#hZMx=sYi3cpI;61S-LQ{N<}hpbEQ{arK$`a( zBj(ku2cz4H*&Oxb#3R{lHm9Lf?5UECmNeASt=Ei>>il}GkD>vha6ql>hEum2iSM;5 zPl7?q?=(ENz7Q|3dC`Ziy^r^y8qfh73ZeO;wSVGM`2%~LsV)Hb>*k3~2@j{DRQj6!-l@^V}Wuks9t?`{8 z07))2cM*kj*tL&bA=w~%RqLt0rlhS%*#3L@jT~dbxMPTw-Wcu6q9IVRR3!YRow%Ao zIPhBC3*=Oz`qi{37;B#X07;;HrliZ64k{uG*F9|XL>8MAZjT|Y=zZ*fXQj~mg%xl^ z%oAt*c~PfO8JUKH#vOs27xKACEG#9a?>SFngnSWc0LKll(PJCnctSgRTsFTzZ%f2$OkHsxN%@RG$5QY~9Y_6iw?MWF})Y-j1dY;)g!YbQn6jO^Vieo`C zb-@usI|%T*gN7Y>2ot<`mvM^CpEKf3_;%nf5ZOK+#-7uYE=yO&%Z9VvaQ3ya{LeKd6e;ZiIP!@G#cAnMjQJzDr@2k z>OyTG5YLsN6DkC1U-6g>dGD-XJ09>MPZO@V*3u<$&90x{)fjNl$kX1vDu*|PjyLe| zH=ABky(2+yw@=p%9p7glBm^tmUs4n!+6x|)Ayew9MG-#1Oydh*9~2)s)EmTbj;p5d z@`6b$@C%SPX4~n>DIkJvK3& zw9#pIzr`8_@aMH~{CIr@e(~Nxdux)cVuU2{UMYU-M6vtxyUzRzc z0@t)Tbi4grX2$dR9G&Qq8+K^-V??mRHjdJR3FQ58!51J1l4VXa{AJ4KJAno#Ho4fe zgD=00UO+t~AY41}SEOrg8{(i`$cY+AQhaJ7LfB5Ch1fgdHDwj7p%-ltZ)6|hAXh!K zj*{}iRLUO5H_EO^A%oo}sfg6y=bN?6;@f7BU;8)zjx3+P=*EH%Gd zy6FVq`$2E7YYi$!GS9>uM6)t};Ma%|n5nOz3vOvH!NCf_fxv&+1ODs1bc!-H{t`>L z`nO-9r5?26@boP8ls|&6J~TC6fbA;7>>+D|O@_a@UQe)yg+bc}%gkAX za^$8{MhWd{s%V~;hfi7P)%pFXDDw_)=cMuE+)TSI#1KTGcAj4*(WH=PXw0k&A0{qw zMJn)KqtrsS0_v1-!mVo&*bsrKwq{m=i7-5%!B2^EPRpL>G=z=!7YQ@@#9869kJN&@ z2}@2HeQ1bh*yr*|lNgn02->D-CBV#YpHv4-55!Hw{U1Ho;mjVUmrn%^{}+_VclwQ8@;2nQnTzv%!3BK_ov7mQg8|^Sw`9hQG0wiQY1v=t zY*j$_OZjxbA;g=9cO?$Dz~9J&4RCY&KVi7}wLxOEgK6O8UxsnI+Z2s|@~!lkNUvOA z`-!VH3LIbrG|or?k2?JJvOb52_6B*xVa20MXe(#nK&SmhqfXw|zuL`vznhJU#1b_4`hm+Uf#X?jU<&0 zWscFmROC4{ikW&4F}Au`7tb;EIr%*$%uA3#klH>vfW~yBrRLmv<-C@(* zSCiHc0{3yaM7_boI^CA`puX`CDUoXB=Q@bd8b{vHdFl?l_o zA`%U{ou*&-LwV7Dj6J=9F-k4<$%ewxBTtb9Zc_}hyMTA;S2vAdPkNJ7>Lu}f0jOr0 z(=<}EBGGz@F?S!VgwMP24~kIdLvcXNNu94D4j@%1`~DHcz9*9Em$~HrZ zIp7Cls%=r#cvM>&7j0e+f#lEbt1L~$RjToOBfaEP$x$Mm7W-aemUI1jgNaJB+Vo_O zSq10oB)P6M{7)d%#moOl!@b@q8&F$3??Tv!ftyK>)DM3U2q7`)7 zK{crcVQ~ZBuTK$&M5cRMa3zY}uSmXz)pFk(DNDk#|E&cy&>?+LLj0Tlw4|B(!^LDt zC2YQTu!f@*W2tM`>z;dn3V!!$QS+Ykg_#KB1MX!Tg)fMUast zff-keD#)j~=mnhDq*1pcpzA&qIpLusw~{fD(@XXCs!McOv}h{!`DWr4b05*=P zCRHk(VjvyZ{xnLM<05s`Q50o`*VV=()|}D~E6h4?Bx#AD+tTnolHXMq+#6~Al5_aF z+0?22_lHJ4*Clr6zM-56qeCnt(ufR0QR+^>Mvt1@O2#VRXNEybRw(+QG2vHX7|V>4 zVdU%_j?aVSC$(~bqJ&BllY8!`BAs$3lQ;vlx9aX+l&QP(2_t5yz7M_DLqPf0_^zcd4QRVISa4p;-1C9uuFrxxKEG;gGpnP} zM`;>t?x_ydOK07>Hx#C%N-3M5kAU{h_M+aQ{#P_=rGvjK#z7zq%TR$A!%&o<-@=Ij zab9+0k>S1wyjbKg3I`}!zGw&y?pxnNJg_-%DM5eIZ8cV*b}X*9)zL$DGle%Wh@`2f zD_TAKYQOQ)-{4j zhFooZ>Y-C7zVw!vdo=+y(-j+Io7Kghn&*USycw}tRjWy#nXGpH((r^eB&Z8mWZEJ9 zeE(}!WaUt7*&972uy=++jAY^Kk5Bq>Q_vD+(f3A@_eELv;muh0hySa1m=qX%0C$X5 zwxC6WHxny}*kjxTT;v&*_=yIY;d%X!v#)mq$r+(VbH=QV9Q9YXWnlrq5m-PV4W1uw zJ!JQ%!Q|Ub*TPj=Ma6w2e>OUX-=kXWHa%FB|Mmy`15c^ImH;oqVALXCN}x_nTORb9 z@lwE=k1}SRhL^VqCxh3~;2nez#h`tEikQ4M-NHWvjR3*?&2l?lEkSN;UwjbPseUGSJdFa;NMiSV#Od$YiX%8Q_=n=yYC9eVrf}Gb9JRc zwPEKg6-*J3%}!AmotwG*_3cY!)j$qyEGuNO)1f9Fv2IRM^&-CR1~QsEKvG5S`O*CzU=@(}@!@9`Oi^{0Gce2* zdi^k%L67AY55m`wF6-yN>4g<34)P(VNPqu6oRn=qQC$jq2a>sQKM|Dqs6tjG1==V7 zRvx&8Cw$Vn{cbQl87usS)&yDJ2T33iNPl=%<0#%D$eUsd;o@w>x`O!Ip=6xP(n^_x zRkoZ0a1$I%p`GEv$txiOf{V=&xH$68AJ9Q+vX$!t)Zf$sV~3vZ-%my^QNgx{xX1Ul zpzl>|Iz?n;BW3MTn*=qI+LuT0u?@R8?u;B`!VZ2#sO56%ael|=km+6GsfAS$iAa29 z4>o_){eD;(2eo{hmHo37|MzSJdV_9)cPDh=2qDX@{!^`kG%l@VOjtuVcR6WMn_)UNWH$280$t}D zh@n+zi%jyJLGCMc^$s3^-y_&F@hxpoS$yAR17R~lrb#a|2Nd5`r`=FRK3!rRg2LMMvr}YG=}xD9oAZ8(d52Pg%mtxiIjW|92Ju87?qQ z8r8XU>>gLc3in;xxV!7ETCQ6yo*tVwb&EMd&Jo;87^9Nv3;#&&e7x(kJ6-i&{XKTa z_MT!cdzktFs3MH{Zs4ex?k)DU)okwB^g)ARk^$_CH=FS<-)!b<2!&XsdmcywEpRh_ z03S8+_63r&tCi|*03pZA_N7JDUQL90Y_4D-OUNaC!3$LiXJdDJUxd9;YgVJuw{>r|FDx87O)q0`F&nASO& zpWJ=k{2lVtyz=n+6X)8W3b*hOKI>mo)5^+6uHaHJ1Oz@BLSdv!*9)nmbtW)F*~XW`|fXsiDT^Cv^0 zXJ~26XebUp9(^Bl2;65kK$zgiVDQj)X{ITrTgXphy`j1FBC+(F?8UDpsIQFkW|LSw zo20D4FWnoRE=YT^Nccx!EkDsz0w1dKxZR(Jvo=~`QP#zNH#DrIl0 z62D#DZ!A)@LY98_{l@>1pe+&&m;vOCd2Aol8JxF9;j8M5)vIoOVH_bq8ZP)Lgy8eY zO%9YDfy1HXvnL(w4WZ1PM}FZfVS`iLRsAzDD$5T9dHoS?-dBL0=3#sn=ZZh=56pRk z&Yk4>Av7Pes=z!E2T+xKddse~j4VGk_AlB~M0ZSrd8wh_Jj>6k@bt3`24H}WacrJF z(z1?Hn5Hu!2(^uPTZtwnVmPjY=aRUx`v|29qAF;CelWay_`s-Luh=3Vq)Sn`7bRs` z$=38f@ezDekDPgGhx~XaSSF{BFRil*sS>ye-r~@lpynHWO$>l^u=oB!loRb_Vej~r zfT28=8qH^>s3-Z+fm|h@_yf-1OUZs7c-CR2o88h6ok%;*J`} zf1>2}tdiOQ$`T&gs^Jra3&LAIHNN5|6$JH9v}kbYhG(Yb$$2qJO; zHFZtJaic=CPBF8K5?>(t2_^@zK9?htly>oEf{m@7yAcw#Q3(FIs~%F7=6ct$5ytNZ zQt*rahE;z@JKq&MwB#)^%*(@HE|gS>(J#3_#J;N$ojmvWu?j35Oi6u26|kZ}#r0zJ zN0as?KW2A_hO|f*k5HbjDTwES1BpV+V*ZjCiX?+Fyw+%;lOaFq(8_x&J}ibXqQ)Qd ziQg}hN$4TLTZnoZp)e?CQB}@;jV8#{bIu0qn1eDN;WrZdRDMvP%_@to@413{lT>oW z6bGs!cB3`XVj%L_9s`s7HxKg_0TqF&S4XJ*jCMBW)2H9|wO$oPX(&1O=T;=ND`x@` zq-cPC}b<6S?$-wl4=b4`O#qwA$;R+|R?zn}-QVK_79*#s&3{KL+cv zORmxfAJG~Gv@$VDshOz(?F?D#?u=ql7`K1I&As*e&jC!`)-!};8Unk4uG5TG@B5|C zOnmR*Rytk3BQb)ztXZL{;^;1GLnbumaE>z1ANB$g;5wyG$Z3XV!Oi0}u(`U`gNmP) z?iZO5V|~j1~(nR;EO@c>g_s`eSd5xXst&$`#1+E0>TI> z+`x|^3b&Nc={|nL{)KUV`$*qv*Q4V1gxVv68%R!1@ea_8IC{jf4b>R9a_?lvAPcsi^#zJ%QJDOYCNtQTi4NM|Kl=`7J3HjY||F7u1BfGZ&aX zO3{-Xy!@mKf&YZ%0se^Su-}ZR;w+7LNl5AF>hf6SB5P|f!&fd^1Q#;_kI_CB>bpgM458qss2zFjndUmxY)U72Et1E!%G~D+ILwsm> zuoQpLCl_G9P4ariaeVDKT90UT2d8;a`I|y*-RC>_uGi+FXR4D-0Q^YhCm^@RJ*f@_ z7dzoRqEHDapZBO;pMM)MD(RfHgzSLfgZ^hy2Z+lJMsrbwF7PVGOW@XimtU}7UExL8 z-#1|B_u}$IKk8>lKW5tFzq3x}rDP@Z@%s9R9&$D!Ub98g#;f<8iB#mN2vru{>ugaW zb;@F2RrW)(NWh=Drw0qWFonhBm+*UyZchVWS@GnI@CTxA%xfFCivFI@fxra{#OE@K z<%8q?^R(T5Q?TD6IM1&u&X>~)0@XImmT|2Z55LeD=n9sFBx+qo9i#*8&M6+zu+oH!?M?3tr{B^CAx36c{75g&$w zMVJcV-0sgHLgQSM9$uo?nvdz7M00-;K6*(;dWd{^CTr+0{wJ!IDb`bWZl+664(1CZ z_&4|Taqz6r~eq zjD2p70kvlqd@irqJ{ zLg>A}FCPbi|Cd|4lh5#!-!OOmFX^Gz7pEZj$nbJ>;&)(RU<0te%{b^=T%kLb>G12s zU&XPf)4h6F6BnH!LbVs&?9I4SttDe@t16}o=t6CHh;?Z8p|_*w02zw^Lr?Pec?`p| za0*PmnYlm}DVR~rJYrC;0#XV#vaimKL>zFyo4@t$8L`A%r)ntJ>^2w~IY8&XLC|-4+7)07oew!o4eA`V0{`~^ z)q8A0kjD`EbWiHb_NzB41^mRDx5cquXEi6|J`(32TIl(VLgxjt%1}HR_AG-IR;^b>T&E@UX__W`9*Kjy3@1#0evQ|gw(T_mXPjk?@KonR- zi+PixT&qQ`^PW&qDaMl$FB_0w1O>8XLYq zu2h=VSJ?ox&_+G!x--;A`B>1nS5izv24r_nnS$X;DncStbmgGfON*gws4%*BIzCrD z(1ZUbU2E7mQ$^K#g%|W@o!3pb=#QasuVAO#+#5?)b{n8%0IU4Pml`Qkva=rn1BZ(T zey`xI$J{0yHr=v>5txO+wPBBuog;@(WN_r6iC#2OP|StrD#&CYHJr4P7Yqv_$(2Ls ziT{PLgLub~yN#Sv+0|*WHK<+IJ}WKe`)lB4i$|SL%)uu)m)reM8(AtCb(~usF%Fo< z58@;(r)5HF-C&oStbG6ZqL!*-$~n;>0_lfoej8L5+{;IMuiRHn$eYpysVlLt(6B_8 z8$|d?uoSYEh~Y@6N(P6bwZIi<&t8mtl{x**^g)s=$p5_JKml*gfO=dt)@V;&g?msO zphwuHAoJFYEfXt~+-Q&Zm665zN0w|HQOp-h2`I3ZRzVPY$MS6ue&CgvV)azM?;%d3 zdg>i(S`uv8N&-G`O7}GT!2oB&LWwQ|>MTE!F4a)3-*+=a!0F(btEOCm+uh*I?h9Nln0!e?1-4!M+QQtr> z-%pwWJj&d+KUPriPyED54Gj1Xgda2IZ|80V8@|W~5o2nUhS*YY0RazPgFzbw)R}T^ zkg)Tt&YyP_5k;49q}R4udX?c`G*Nm0L45y4SD)`}${{bPQG5zG+APElxA}VL&c0$? zJCsDAXhA$U4D~Q^QYW_hggD%+l0&Amyz%#jo}=h54sa$CmKriXfPHK)YN955JI~K# zX9hb#p6K^{@$rpo(5gN0)$N4oekE{PaE|#YCg>8SLG5o3k<{5bYa~&g{C*(FN&WTocUb+k7ukj>LZ1+Y zw=Z}9^WKiFUQ2+WFd7A1-I=LQWkKUh_z_vo9!?`g)!)d>T{p(Ra;)*7?^|G}8Dy#s zhF^$HOrKzyU3}bvH;>KAxa(d*! zWvws7!R`JYKQ%VoG-gpeQ}oYjPF8F%*YlH=v9|(F&@bmS4sZcm4i>{EKw6{iM zqMV4eb+IyGXf3ibW&ncAq&s_Xs^s%nj4tLiL(?h~6W!=mt|01&n6~=0u%EHu%Hi-U z?ND!N3J4|S=M+>07A|ASbCACL{1vj$mj;vNf4fCZMeOKJ>3t6 zK26|q6(Z)ncSQI^w9^^d{P6`+PdYs;RHv(bJc#gUcN(aqftcK8w3lj|=6b4}nC?%+ zOPX|F*aE6`c(@sqS-!oGDIelZvhuXrFJz9Lf0)`X$XI^KM=B`!jp52#&qo^{bKzeL zF@QcI#KDk0_Wyj4??_VwVj^IWVUO{cn?y|BC1z>O2JQDE9>uFY~Q#1=ZB&6m3g)o!TtUqk`Pt2_Opgz#c&- z!q0UkLZ@o>I>}P|ni67TS}9>aPF0Eg-l-Dff={)!z59P`r@eLg0s1Q`DBdpFRKh-tdc37Y0dA4@*&adHH?PIphsmG3Enh zX{12{(>H_mT~9D;g{s2n+tQ=Y7%HIbM za?#9_;<{!l1-QS-{h@(|^>rm0gG_0$45eM1QR$1iCvZc>mVEPHpCbl$Si>6nRu0)T z<+5${G%YVbI_uL0Ox;p|puLn8YE`kg2C={4yUnW=jsdzr@&QCkVdZZaIwWR??;sw` z%7+94Ano5{(W9 z(c>vNvi+@s!m4iB*T5QIXSRsCO8jA$R4dN&@rJqDzV}Db4+N!Tg)Xsfu)?J}yLP)l zir;$Hg)VAzzOAh8Q@wm?J){8SzuJAKkM_KfOJn$kI|gIg_u;o|v&bLM{tMz)7;ha4 zGXRNJhWfbq&^YdV72DZUQvQW&|LJ?!Q-;RH+vEz^39D3a2G$)2(Ma<@__RyV?}>Ow zoZodu6m3Knq(Sk`0=1}KYfSzpQTsr%*dV;fVGX!I2A**|-hr0NmIn`ly0k9_bJ>gh z^yz=LMXDyq({GAOWi_xW)D=bJsiqD?+WJG5LZS0tky)s0i?cC~*uOWLtpiQTSox%; z>O_b8?+&joK_Cj*IF7bEn#8KC_aju((s=dzs1B?CHj%cpsA1kg^iiY#woC(93Mr#* zp&Glth44OcLh!G&#%E)n*@Bu$555(nml6dogp)lKE0(r`xz;vk4O9R{F~^{y`CZ9; zUcjZ(EbYthx!(=ML+$1bm%B-p_b58jaNa_XB~0X8-Q%-r_xWg-o095E2|6MRp|j!v zr5k0ICd~{xY|-&35smM^A=b3m+|1;DD6qo2pQdt^YY#d9+~EI4Ah~33c@Z-Q-)AxxBKYP z2byonx_4N;cXU*gNrDm{YRwiGx|f!fpGOIozBZ&HP=e+*YtRtJ1aCewx-uMZQWLeW z*K?MWIPj9|`xiYL zGeLd@7ASZQ&?GZ%ryE$|ToYlhR&BC3FLM_H6dz6)Fa@8b1h29pVSjo43^sy2;r!Br zAlaTSAgChxFhX2NwsXU)q-+ZjWae^{P^$Swmed9POubMqM)&RMC({9crhvXQRlX!R|>7K)Y~N6#gG0aj&jg?Pid z%c#>6#H7v)5zWp2~T)#AQ!W#t6= zA693kmliSyZJ1mZu=g}>f855BRtJu{EX{hKGj81HgHx*0r0SI`Z*1}C}G4$>G_MI(ju|1{G$cSZ=d zDWC{-6Wk&SLZvPzEEEJCP@x`M%0P73r=4Q(WAO70@sty>x^|r;m;%~rYqli>GVJsR z#h6!c+GV@>e;^MwT=;C9?mQ*LvDDF~W`>Ki+qmF?MTZUe?w?|Imft2#@3G<9_80RJ zMfDb1AnHvR=Q^qyR>a>SijtgzC>Rlov&mAU zL^{7L;npZ~=TW)N9?+{97r?0Sx=!um@~o_|BzxTnecq9Gl$nw;JlfdFh*p0BnBX7! zuW(YdL<-MP=6Q#M97tla1)CjYwN{DBf~lPbkOD^+0rYeGC?N|A(>CLU(Qw_@5~z)3 zj<;ex#MgKojpr=`Y5;nn-QpG8G&~dy+OkmQ=zX8tNw;UJdK9pH+C*k<CouAo%TrYg%Ww$+NSdwfr~3Q8TFSox%8_VTMdER zQFPuY^25!`;Nj#S{(~`UxsR`1G(8VQL209(I`qw-v**9MQ4L^r{d8mG1Bji_tm_Ad zehub9>f#Y@+;znhuJ7G7roB(2tk_TYhYmx4@p6f?tK>ymOSG5s0_`bDHnY}7TP;$r z=c>O$nY6(Z$t8&Ozh;1ybKhPgH8_?<>PjlJ3*$8}!mSr0$D@rO;ZI>AUc8W@pjE&s zRw`5J!3YJ3XOho-!xi+N_YdG?ha`dj!~g!T>v(B9QegpIB?+;iN8#XJfDBVSJzv95 zW@*YpQ6Szc8M9SJqfW`cclW!M0u6)OYhUJ=y>8WQyj(5;amGm#z+Z1sZjrqD%mC?o zkC|E6BK~Yi#1?)WQZjen49V_6trG-4h0;BKKs_Iln7*@F?{b3bS-kmA*9#?=2Lm6} zQ{?kXcw;g*&~o2c$AWR8R=N3<=naDW)xJ2Xp8Mb+WLoqO{bdh5wA!y}${mgqNoOaY z)9A5}MAKR79YP`E)MOa;L?8;X?ZR~Bl!fsK8;sjEwXEdahFA7jB=xrH-QqBx=(xaz zF$`G9osqxub25jBGLO!fyXut%#awxsITd8XGC#GYX@Q_Gt%Mvxg{a47w(BcJhtH;N zIE!$lp-bq~mZQf-e%6LRDwBm6C!ZE#$5f@(D`;cun&{ zLLMPbB#kLjn`=FLcR>B2Pb4^^^Z1aBl)}A!=C?aVZXP9e&M&P6MB7_^W-faQ#t-61 zdBDF!vP~h=^<5K9&F>fO)RX+zm|j5LW>}njh=ai6-Td9aYW51fN1?tA)LvYdf|LK! z<8!vcHky?3A@YMFqbqF+S00B*7s1B*h$R7mbPb2uwvL7*>nLT=-S_AhTsqV^H4BK0(gj8 zOga^z&1uDX4#c&ZaKQvVWl!pT$~$J^y@!Av;Lj3$pOY78Em1HWcQqvBpw{s%$wLp2 zVK(FUzVBPqpMt^_oV2<_cVYVwzHB%Y%lvv3c;5FE;LrT?&$~av?5hsHGZ~j=%ZOEy zrF)-eKEt4ayYy~w;n`n8j{r(WckgJ_>)j@JW}Krn-eRA;D`i5lW%!CRmdmo6pUC13 z=eH-T0y`Dk8Gf z8x&TIshU*C5<5P!zHSF-BU_5cs7ks*&b-(@GF?C>^R|xcjn)To^n~`iXW)V8AO6t8 z27?oWn1Q6qJI3G6-w?hX}An`@~Xql>DIKRd6qpnx-hGQp3 z*Q+AU{(AL(UpVe1kfa0S<8?b1Xk0j(OiyK6LKwcMON~5GZ$(rfo5MyOYAaO%(bq*A z6@&m7|HiMEq^^V(Y~gcW`vuxBU*zk3*a|PtwMB1Wljq z{a_n=#88SwOTwU`J{H@KIZuHr>~DO{yXQlm;}T@Bu$lY^E#%-o{bLyE$NfZs<~LHn zUR3=~(9b^3*(!x-W&VEs@Wf5L;dS8dxflJqr}JTK}~gGcD)H}`R2

Q`_BfLzus17D2lM32)B;xpMHzx-9LJKD&`%V`HRD$ z;&gga;`gEL+Yy|W%Djb+HWmcPTO+AN=wjgRH0nOr?$p70jxp?O*ycEBD)h5D_f+y8 ztC&Neyyw-BvVI#6kSpjIV=}6l9RD-jv#cSY`j7dFZfyFVe?&mB!$;SAo;GwKs>Qp0 z6Zq4&MPqUO+HG){N7b->?Msq|H|+81EUDCAK2JIo8cO8;O~bv0+!BXS)Ff3!6R04{ zyJ9?UOD-SenC3;n^ilOPyqt0z0b&854WcVf z7BNfUNi)^DpzzcgUPGUCi=*xM14xfGPtxfoT<8CYiK@mOUc!lld%%5@oHk-Z- z-9O4`&T_VA=9JC~_cf5Yt7+MLUP`xNRH2mvycbRsFPMWrAzk{}jp_kLM(E zn1HBd+XG9Uw$LD^qz&BZbJ+#nt-trNmsEeDPt{yw*_Djqb%U;7YGA};hDXPfYA){^ z5GQ{)wZY!bzFRl*6T6+VMkd3am8`=}D+jmse++{hVUQ$VHxM_UE>FcXG-)Y$)j{c1{h!54nAqR;etu*1NGn`CuoOsMn;x3V8 zDn+5L2B?{Ami-MiHFhl6CJ`fi|7=CBCq*_%jtY zs+rtfU79+8qJIab+ z9DO5fNA1$NifSBFHNnnDj7Po5G~N{p$sud}3wN1a%&WgouhgP8F1L9_#58}pucpn? zf+(2Y`N6Ckve@_YxiF)NFw||a)|jT9xaDI)v#St%Nd(#$==V+ve9%gs{tgH;=NVQD z@da|VeTG{wYnjJf2O-C@4}@Q5AEbaWcpb~Y@T7yy6H4M+O-)8}*sNC8LL+vV(Y-(+ z{I2(haBC-G_!HXOPo252jz;D|-yZQ>|6-`xLc=ssxXB|#`62O%d6NZ71e1f4!?0x! zZ=W^fKmYs}d8s#`nNRP6*YPITY}hf=X2#P=r-}}VwPlvfIDnBE*q+;@-DhShcbxs+ zCHGTJwHvvi=@Kh#wZEbfah~D;x)Nw%|Et^$&3(wWH3Yv)VBAitLyS_9@GOTUr5S2e zI=wki>doh2gIwm?ywT=|=L>wlzjnq5HVQU;0*{MG{ohtI;2_dJhQZXEZPLF_;^zgF z-fM+rG1SX*=B*9sd@`?yzOx(9@&)F~Lf_vecBr{|4b{^9kxt^|t}k|We6^cQ+6E+7 z2uCLX9>mQknr1v-F_wCJO9D4|SYosGepf;nx$^{Cbc&(WlpEr~|H)1P_wtM$S_21! z?EU3WqMO<}$WTv8@_@DQC#;7)B&e*RuWBpb?P{ofWTZ_wJZOpkad13)^*cj^qcE(y zDRZ{rYqbv{2Z$YTUIP*1tc#1S@OX0Iw(yT(4#v1V<0nhByMS#y>7u&Yp@czf<2TaU zZse+OCc+tj8~`g85#~xPIo9YjT4}dWm9?~*^l{fPC6pc%f_RRh@j&t`Xzr35PE2`S zoju3>?V76hSYofla7gv*XfacljGr;YqUd^`^U- z*P@KdVk8`%VkN#J+arb*5mS2U9h<6`yR)4-#2*vS3FA>_gHG5=tT0eeeZq}^`^;!S z%^Ysvm|#^YA{#teeqUK0%s^g4lSwVc#BMv1n`6&PyPhSZp7dY%Y)a%SjI1T+Sj;1p zb3DIpnp@Y;D>!m_rs#5S343=s{pDrCQZLdl2K%ah60Iu631a&qIcCQ=ZpG>A4mI~X&D{Ww1s86^{rVT730ZA$#P-1xURZi`yt&&4& zSqtJVnX7(u>NIm9?lBt=RuUZKyaRM+3Rz4;W}%&(BeJ=EHdh2)i^cgwA$&rC%7;HK zI>89zwx*vCyz}ColhEX?vwIWNcdpd<7s4|3#NpDRDP(#Zw{mzKrwO4)1*PZXC$R^`f=v(A+C9?iV70LAVtulRPA8?|!lU-7>EP(7? z5R(4c+n||KMe4n>izi{wV<(?uPYK`_F!bu$Yv@z{6aMkGT5P%|`;T~%?z>-0+YKX} z8a1}le2F0z{A}^(7H~r;4&zq({^@wt*$%X1F$dM#Yg?d6PTu{Y6&PWP=NIM%rnW~0 z66a6)QMU7o3UfvNLRbgGMR(XQ-gP&#v~LH!AE3H_EwzZS>4dEkHy3C{K>wEw_hS03 z^F8p5E8j^b+v$G|(q{YcM8Ql$SBI&~%n~o7AL#On6OEmCs`7@2bKof~x=RK9HtKXD z>l>WzvhpA4ZVr_G?E_|^n<_-akSCa_4IgsX?Go;|jrc6Fz>ODV27vxT zIM!#TZl~8)Xea7JnU^~6K9hEwI72IWjR`lw@VqG|}`mv7tSDR+8#5BQ`Pn|Jr=!Z<# z?c1Z2wZ14tcLyq6t|(mYg2y{x3*Kj{IDbDnn$UiIZs9(&PLcZ2z>`K0zBz(yW#JILwEA`LO9y zk9-d=5oKeWj(Xbtn1>nj?@e#)8ts5cWh=frG>GGx$g~sFh`($uO-NCXKj)qPgO5NN zLAAoORwvBm?~RRJ2{~k+V$nRrjPO7N1wxpqWzy5({(wZ4b<{s4N9tAJu%R?nbR?<2 zGw7BeOj2H6RFZf436yB4v3xDs%MQOlRO3bP}Q(P zid}%85SA}YKo}m_O1N8akHy`W%e%$|vUPqp5}(ZW@HEv;_mF3mo?orbXdwtHPZ44o zsJU8;wWY#Xga2z19$E2F<8LFKf4zH;A{~$>T^+lpz-Q8Od|ixQO!58H4_JS21!Y-jx7e;5{e5^%d3CY>iEKUCB$g4y*sligw{xzD!p#yf}K|m*Z7je3B@G- z9CCr|UZna8?Gh#5;6GBPJ$~T1q8*`svK|rO%8EfxEb^pO-U*1S&=3s`{&aAn@TeJG z4ElclP*#&Gsjq--{Lq4@DVw#(6aYBLsv5{wQ)@yM-`$a;W)b#+N9(IF{tgu-J(q8@ z|LFu|4#qP5NwvYxOh6zH#Z@++eT7X(unead*bWQ$(KYPfGImfDY9b(h!d}42ui8)k znLKwNYw<5H17&^qMD>a9>j89$RYUC`Lc?l1&8LraUY0QplUAz+aDsWd2WUIBtdNu` zR3y2H?<9T@OCNVM(Ca9#f1LL+RQ{ToED#9pMkr9uq0{(>yu0p4dmQPPXA+}df(q+( z%kwPcj#&s?>)fZX*oyn5&XJRs|0eH~!=Q-{CBduoA2k}6Z?JGwbRoq^K$X#dmYbX6 zSI5XmHy@zN%nOCJRCTD^PE0~dVJRS)`Vb4^+j`+)DNXWkcRj96J=Zf4&03kZ7Im=ZMtbx>bxQ#mxcf`$sX|c8@D!<3uRHCBZ zDu4;+ho7)tf6d@YTkf~nR#_l_D-qTmqVr?N+T z4cWI%18j0@dpF9Az^44+(yQMpKK*%gNF9n?xQ>l|D%FsJgH^2cw+REG#0(|j)+a(` zkvDT9-%1a4ymRPPK2)s1WvcF{(OU!m&M$;Nkp*apXG|^Ao859(H_7x`ysv&bAJ$XH z2IenN2FeEmhqr|v1#?u!KOhrdMa(@-$Z6K>9_&$lbSUbq$pQN*k_15L>At(WeirWS zLD%7PnABl^Ru2<`(w5OfDFFxdx@V~yg0M0kd}e%)kT0)z%Ck6>St;4oL(*PNaT&v_ zC#2LJ`99?xJPQK^g$A`{k$Tisv4*KE`glVye*nt?@b@7$oJHN^Ijev1Q2D^MOPTCx z9=m7q&fBhMoqSmU|N zLiE_Yp{Ke~8e}`Mgbf4lF8Uz=kzSkrHjrm5NZ{gbM_&ATT&A)}5Q8WI;b%zcKk-j6 z!2dG^zX5jIN35nv_+HW_ENhS5lHm^q?a3P+$Xu*6jiT9X_W-SDElN5V8YF9` zGY8}4UQL^W z^J4J>HQrOb5)g?&dYpx}AF7np_w4VX$b#hLFPJx6S^RBWS1szic-z2N?g}KU>!WKe zKeC2#n$wrTrdW#d-EF%kbkvX%9J}#vFhUTLL~H#Xe>mB?h$Bj>_P6kx!w%Sr6{RAf z=fINN4s4=(6pI9^d~HQ^`QUlvY3lPrVoT+` zcxmZ(<^3G#7-K@r;GelSA*q*~Ftf{MZg1LTM-*c``xhHochD=kZ|#`CO+gfiaz6#(N7+JY5s@!+kAelNi*XL;poEeD6T;098(rX2>kz#O<;!Q650p^0 z6H3%jJ1IBnS*%$vjE4YpwFLj`{qQf?Gf-)kgOk-=`C`1V^CVZM^MO6zzz&3Zh$Dqn zQWC%Wu94~LC#i~9_w`~rAZzI_fP;6X!y{Ry(=S#`kYq!|15eNkF9#ATX_W&am2K!I z1Hpgv%Y-=_(r?O-n@W_zxEGIM2SLS>u}lu#Ok#R_WUC{yAwXiPME=#2-nLwsV&!d6 z*(+^K9Y?${ZUb_bG;;1I^#~K-D4t)&b-5Bvi(?}>e6EY-nB`AJ_|91s4UPU5b{fY4kfS6xNB(2|b;P?T$eu0*VR1;04EgvEfhmV|>L*8a1TsV-OY1_VUkGV`O&hFh>*^xz6KJLo@ zy0BHC?UQfJHs!+VecsAzn~q!_8l&+%g)8-NR_J;pPLquS^rXKjV-{p<6v| zq@;Y4BLr7?gN*7$r)F@2d;Gt8JooJ{0upIkDDLxC{#R8N@B;HsQrP6$g>^=?=k!C} z1QQK3l<*LBzNtq+#9QyeIIuyI7rj7Fz%S-k*x2n0xhQ7RG45%Ji8*@`_>`5;;W>O; zNd#jKexNaX<;pV;HCiFZ=_7Mx2!GW-+n^Qg+bUd96Qu$)7t}RK8`>arTM?e> z@R5zxoo@LCRRe2`(kXSlAWKjdba+qHwD52ZE(lc5i}E?oSc@F^2T@Up%YUQ!X&!o3b!SMW`WDR zZgKoaP>^J2CcIBMX;W1jju9&=v=TFC9T{t<45}ZWp98nAD?h3O2hPmRMpIDZYw~+q zWUM-FbMdQ#zPVUm(U-fe?WRbA z{@R8%BM=D|Ha4!GdTt{uY@8g)U{| z+{`FDDXQt_%54;AoK9V@(`c|pT1E=ZO4cC!ArGrOia2?P+mLK=mGh%n6`v5gz>dA( z(~X`{p!nSTfUH->NfIH=>?1Z|b^faMeB_edk&iLNp9nM+z+K$)v}&WATHl#tRN=hl z7Hu{j%6BH@H=0N8;d$k_<47?otbs^Xhn@oQMdTJb5(L-=ywG-d%v-CG*CbOM;V6n1yZ28JsbQdjw?o}HS-{t&JZaO zm(td!wa|N#zJ<*9Xs6x&jQJY0vCLDBf!mHY+pqitUZ!pD%LqYj3n?f;jwIzx5W5e# z2VI;lH>FXR`f2OKFGQ#}smmiYlDIE{e4bDf*?s(ccs%G`P8|PRsB$`NT?|88w5Xjy z2N=C)P4^@M(zOadt?-M;UZr@o|jD-SWLf2Lj7nn=`Wg_ z=-*o3jcV{CAx^^1aKN*t{f6XSJD1ohkUr^56zwADr`^{G)FhQAnC+`Ro$nict4{YI=50l1sAv7KPkqmjcWJ-^&-WIa(8zD^Zrt+&bG_MR$)5Q-RisbtlYkVqbA= zKWcPT+54P+U5aZ3?dN@~Er?e5w6SXJo+MVdFL5b;JY_-Jh)T<>?3a&Jp1fnc43cv6 z8vrVi3>y7Beo<=< zVQJ*UsfuNiPJ-k?Id-3REs{a_S;4O~Ozy(S;ZqBP7}Ym*X-U49N?LSi!qBPm`FX~# zG;r4ZAP4M!T==H5c}~j=)jP$zRzF94$-Lc)BXzMcqJja~Xa#$T5;G8ql;#6DLan%0 z9^%nYX;%gNp=Hjbp2IH&^|$X4aw&0^|KcfDt|C+#nPyY6{|7h9^$=GYI;cniUR}qO zpOG(n0I1WbT5|evIq8W(S;P@LOA(gHPYxPIbw66I64`DR{TQSn2v4BLdSOjJ#rQ_N z+cv89)iYhDq2IhvTN~*}q|Wg{Hx4l&1+his^wEJnqv-vZM}A=8PWQ4*B@Ww=P%+OV zHD=Z-HiZ;gJ=ch&Hnue0Sv^6`BLUZdKkxYsbRA!lBiknC28*?p3k6AOYNO(TW=z&ZH-q>{hx zNm!w}#n@4^u1CV?QPu=^Q$zwCi^!`A8EfFGY}zoYagW8 zi`ey3U>WsZO}e$}{CUrdX2SiuD;Qi34OI2p7wn@J)QwW;3nN#}fzv!(=XY+4a~ck6 z4S{SDXX!z5TrJcSzR#9sZEO4sekMEJlcZKqOWIz{N6tUTA3&x=W7!!>{n2y8+?Y(b zXA3uU7D!~J&4}oBHK8J;U`5ALc&%3K*S^8)vE!E-xtD5q&O>ZCQki7C( zC{Aja20YV0u8H@&d5tc)E{bcTgj$>;&%ntB6h0cjZfW|SBv5fr1PDuVFc42f9^d{T z?tBb8Kk#Os-CDWgqGSDu7g;o(4%uMEJxR)kQLseyIhupn2A~XcnU<%QarHpGnkfWZ zB$hFo93W9l#S?YAQ{nLdwm$1@-B0*9RGW^`$CW5xFCTdj z3)&bkj-UlUc=~Og)ifeA4cSXY*y;i(*YMUP3{Ef#T|koNTs)I#9CbXbc~Ci(0CZ=O zUfYvkObYt6{}LhaICCr#)HuY{<4n>Z%1|0j9vimtO(*9HdbaMKq|1o97ggeQcx0N# z6_|=crCaCc^3I2baSh3x`GNcfkaJbAYl18W*uwTzvUa62^Go)99(ByfH}&YcDswJ% zaf+3jpMT=e51eV}oBsj9V?iF>el0H=GkW@;^i!P|cxda6 zA0AD1hPpJ|yCzls2`u{g!bl>`zRzmQz1~W!@Fo7CftscIPlntQJ{gcgvzHuCWFOgv zS#@aAHVdnu54^6Zk{Yv_qC6XxL05@mA%*TcgAtujYE7p!|2ks7Ghs^nttN6Ed!jB- zbOqLn_p8G_Pw)?frT)o^&I?rR^!{1pmAPK)@k|n*TTUT(~)lBd9GZ@-FW^t#_(ZMLcP0Zikf>eZ_yH|wjSwb%& zHfVnIDS0U#BAGSAc05YtRKvw%D5^8=_I#jWCvK6srw~k&a>d!G-)=PFn!`>7(g&iC zF*NUMZ$OJGGQSkD^}SqN3IYA`!xx`T;#xCKn{E2CV$>;BgJ_VOX(>oFfEfs1k($%J zW!)g^(DGdw#28b&mJ=tP7FE@vSC+1RzzZd=L<$|?p01r;80O<|{W+AzS9$TH*DYyC zTW4kM0Sa}b<}){-E&?lISzQ@5vX7kzHxz^40!)GMO>)cjR}n`?-t`0#Sfz6C7LK$) zY=y8nX!x?IJHVUk20Mtp31sJL1=k#kNjr3E;Q(INe7UCplhf05>3DjZ@za^M^+u=R z0)lZnxSKrMq+*pXZw5K z@j55=K$#~heyytwJ#|)*Gl$5he=4-YSXS|ppK*c^JJkxN897-|Ga_yt*p+GRXQF2^nGBvZcAmf&5VWz zXbOWP709Jomv|+WYo2T<`)~?v+;c1aVV>3*@dao7u0qI8UJus2oJIq1x5YLR?S^`` z;%1uocxF;BU*IeS)x52ZF5l-gha6bV{b~Z$@O_lGGJsyRXb8*KLlNn;R~w5WtXTOJ z#gF{)Y}RuV5Q%#yHUBV^Z*IP?9j`oogVk}Jg1^9cSDCEE#}=2S)@_ES5A{X zczKI1P?Gm?_YD86FpE~?yn9JQEUc>#2r$ByX0vJxcSvoxapH$~dy{u5&jgnrT6HLd z0_A2+@$Mjc!nX56gQ;N@`aaI^B*tx9r0JpWyAk8@9D|#JB^&>#)qhI?=TLm_zLGk{o*v1PGGU?*o;9qB-vAr8{Y;8m6S^qQ2$YsJ%6z2 zO_$4F^=x4-Lc47+n)1QT@aZRO5nl9pXLr{(f6Y$sY9@0F(bN%FLn7iAURd2QZwIx` zDfWBQ*Q4w-p8Nnd3v|MF7v*X@d{RNK@tUe=KM5U0x-xZtD9VSAI!ol!p4_vkrjXxht4y@O0w@9**28#Dctd=a+)HXN2;od zdabgofj_Fw=j2NY+S&C5FB<8k9kz&T-zw_8pw=a$O%B4*rFmPKhu?d*Dk+zw)kMV0 zzn?69M_b{h2&w2i|4UFM^+%MMZ)saBRD>Py_76R3;@gMdi=fylTjf{of?)t5uxUPU zNEaWkYM!TI_&ID|8&i^su7N#+CJUm-%_T(I{WYm#qRCxdc&KCuo5$xJmmfziMbw{L zCj``iHc%(C4k!(gnR^8NSqgV&=M9OziYWQ3L|(Xm9hZ>lWa! zEjt2CJWSq81z`=aVoDa>kwl{LH~i(JV5Tya3%0Vg?MvObmlO!w3dd`+(263MGtspA zDlSUQfn!7qb6QW6z5GtlErzr0gUK|W7!0{L8f!BQN$!M-7f})aBne%Y&>C?5CD^35pV@-I-o|d22wTCKIc*LJ9Owa>*5zrn>!V+=55O z`BC6)S@V&N)>5Iemp69*LkV8ydr9rN4El>HEU(>OUyCH|C6?E{ddE%;FK(CIMj)$N zyZaW*<1aT&2b$SfpX3~zV>1!STwu{Y!ZNXvwBHF4S$!7b%8Fh9WJB{n2TGt=-6nrF z@rm)Dj(-#^V)}|C<1wkdmSaOw<3BsE+miRyw^tvzXA1F#R_;V?$e66sdE8YVRkENC zMvE;-fJk`o#`^9o+7)G-5Jj~F z;XGj7ZsE{-a+j^z!-sF6Y6PTpx>^!~9{?-rAo54H)ym0_Mps{UbX)b5_YJ2-e-!ni zUxE4i>pHzd6bp)jOXtLP$xJkF#1x-y6Z`s$t_xG}kKCWU;|06*gs&0^3*iy$U1c>| zpHQAs@m><#>891G?=0@T1~RV|ouzVdHeVUCWFRdqqC}a%|8VY%(5+LGJTqI048NNV-_ zS~M+kj^*H4{Et^0Y-H{sxWIq2o6e@PWEudi=TDX6yF0hcS^(=}>%j{Ibe>A~Vk_~I zD~Hb+_Ol`RCCIJM^|=;*%zrjsCIHXX3yX6~Y3fCNLrNIr&A@3H%i|C=UUL0{hyL6K{fsNLqf#sgvM@E6Nstc&-k~|KagK!W~jvl0JNi@uFMrdT!etb3#r-+59ue%`v zTF>EK;!ydw9DtsPCH>Y6@)%~#DgC<|@r7k?OS0g`dV31SkWw4OR*3F^d922BdBB9l zhcsmCD=v$kQD%Hb|JUlZfd9F1+cVrax`AS+GuBuZjx3`nr$T+_=lMLD8#~7b;Q#p* zU|O8_=x0nala1Z!Tz?hw43&C!>z4ccR?kV;yTFNJ9H>41{5E{G5lMSz>$I2Zq8?WB zVaRfKlQj~xjJ;4-N}z%WFp+)dxR7>pBU^}G4X3>~M6v2msT1E?T|~{+ZSIGn!Golq zN0e}He?^jic@FCv(z{#Eu%b*+>d=Tn=}fq?w~V+7>45xeOgndy?4o>&ZHwhlJ6~wf z4V`A)?|LvPk#I-My3x!f;6Wr9-{`UTO17z^gKAyX7bzCL;8rhEb0Eu`6_NBfi?;xXFhxwzcw;eDwzne;)1_ezm(0$eo>i` zn<%dH>Lpi!sFk#SdxM%io@X9))n!kk9B?atX8pU;rfPJ3aC@w&J^9HSN&8RylC16| z5jeP<4R=g{uTluut2DM#MQ5ua!zZlCX_|9??3&FIDvLSh4my5xvk3N8)`R+18@!Hj zmlEHv4Bw^tNMbh)+-!w;`pafLuN1xH2G6WttS`$lNbQcyU&rt1c^LL&>@-7PhJ6=g z)^BT%IP$Zk2BL7NaHhiwYG;`6vdISo4w4vBrI`-mMv zN=g24#y-&y!%FV^@&dwb2VZKb#EdlF!hk9Y8};t4bdjJytyOGJQ;nv`Og zU6H&^+%uc?tWljrcGjO|>D6uonYpix$GASK0;KbwiTJ44)4#-P@^&f9L@06jNbqLd z4-enJ$Tll7kp)6J=x>0>&qH;m=(HqI!ZvO)t&Bgii&y!`J zXOq3Sb~R;-9eH+Q{}_m%BchkOnFUi&PYfymcTPo*?jvPLI+SfL*2XEGtpjk`N5%K& z!OmHuS%k;1tIOV5^}!*67YCPb3R#JIq|qHJP{JAda>D3XP}6(vXWgi^&=a(cYKt1a zRN2WQDkNMU=i@|q@bE#~S1n-LAoQz!bN2D*E1$}29nSzIj4^nOGy?@rX1{lm{`15$ zq7Si#4cE7ZdJz<*f-7=kc~UHY0n|X_KZINDgP!}sTtY9Gk=kO=vJ%p%PaHk z31nY$uRzrhKP*?-vGkQ&Gs_p4sG|{QA}I7X*--HLo2018fej!MxDQiYj(r*xw9Z~W zU&#h@UX)eDN+9viU0dt2-74^c?;HwgxUtr+1Tsu|Zw;my|1fa3i>|z(#hTyGql7D9 zZV!V1RPasz4RAacFIqE_iMZBQ#cS%M6x49esWB5h$y#7zv_j7rZkS~RDQEOcffZXo zm+z)CeYuak!dq`1vms`_S+P?sK3ID9#md~&f)D5&vd1HO^<7upN{}y}Y-%8lK@m$u zYp`}8F|v2C3+^rpK$tgs36&hXYq9A<#2VUP_%m)+24)qB<*DA|dH2n@3r7e-i)(A} z;2Q3?EB#kLvOw!1|A;-;3~tSc(8|n0;Wdt_S$PqLq0kUU|sL4asx^4`xgM!`PdGl z_8Eo8_Som&gm93~ewsiUcQX?_m#eH{#vUV#?Kz@EC3MeI8vjZt9JZ zcFx6f<0gIout9%z1+1foI~dDDXk9g$Ju*zCH!SDFFEw4Ss9Ln^d!bXdD%JpmFXOF# zN>uw$j$+Z#8kZi*fm-W4$3A(#lYeG}GqQ9AaU!5SR7iz-2*L>KZMB^Q>$#Nd*zhLa z{8^1k>^L9=T7(i37Xl-^de=c!zfQ9I)#Lc#t>*mW*P0o!f0^*Gu_9mNSvFywS^JBP zleVZMnB|YbiAN33oZFR#*!|=FLRg{LC})S{s>`JI`0?%UElB19;HK4t*;;`v+pbaTq7x#P zbEA6G)DrTdVae`RSRc6I;+mDq5p4ba`hG zgbbToTRn6Io`z7%i;TY!{*M>I7YP_zz5@Gg^x2M!@wo%7o{AJJ9`=6)N&HYSM}V%zEn?o_{>&KW+w2KP#*E&Inb^T_iU`{hwWY~^AKrD zj*`wR)|9fH^Viy5T#N;Hh895P+P||pu7Gj=Ieoy5pgJRVQ9r|2vhniOHZbR7ejd}8t_qq2G`oLcr%*LcF zWMcoB;U^UK`grs70F?!f9)=^Ehj=6F7~13t3Pd7CeEyAb9>2mgZRDgk4|z!bZA(?* z;T0L2`*Pi7Jiqh5Md*tFS?@zMSVYh2L(OcGUmeGSeK;7^q^lJ-7*eV~#Qob*{ttwd zcP%;#PTv$cak%Lp+$rvF6KtNoYI)(lEP1eNX;F6#M))hnAU9AX-O3=bdesw+`TTlo z*9~e-qKHzC#LDdYm=&E1NX{eUG}!)rctgc>lD{!q;p7X$L=WdlNOmv~x;nEx=ZSZJ zifJZZJl~llkBn<9u-4NPYQgokl0AApW5a&9?@R!V6Ihp`@VE5|y==(D)r{hdcXc!_ zds*Vsd2-0b9E5@2E3Y#H*{8wyG~oM1*%_UC?0ttLxOMwhdvv&;DJGn&kz#JzHh)J7 zU6bFa{Cpx;*=VJ*srXwgTB@De%X%-Y3$s#jHX5xdzx%x^)b0>O@DM(ay(nRw@`Q^Y zNaWn{Z$=N#4=B_A<&zHJS;^Jf#5LZ$+JE^R`z7aR)tih!B6wnI)j=vxnw&ncYxpN3 zbMV1-fWaw65(hnl!WNN8Gm{B=!OrNh47FXDbj^JVpNN#~7#Esq| zVUT4ceyrNZc5h{YMzRoUI|%6N00iLvv&j+M_^-_^n)2Z5Jj}vXV8&aQ(x4S#Ts3Qv z$1=Ge3G()7)SwO;!L0uiUz*S&ce(10S&d*>zWaQd(MpMIhKv-AS2XsyDnY;x8Mq`b zHuvyj*s~oLYMdd<0=}>Ogtf&p30Qh-&TX8Qds0E@_p`%?MqE&q)s?FX+3_HddWpaj z!^Sc)`rI{A(8wzTUj#TK^f6}IR;?wEZVTEv?%jV&6 zb|xn2JyGT?TK>wdF3b3S(nB`0?@)={|1Diq|6W}%St(2XZv_R+jAePF~Xm-qay=XB*igvCT9G4)EgdKc%V-=JoNQT#0yBr`N%MjqQ~~# z=oek#bOgm8vkaxGr;Ff)g^q#XR`P^&%|bM@p9$fz3^^-^?_qL? z{nl9gLS#Zvbxv@&e5_;G)_+Uae&4^QiGy07UIb`fPLE69BSYLs3L=jBs9 z(24Idnnq&EW@1qB{~}onEQIpD`;nUz(Ul3DzMz1UAvKr?q)ek*IO)Xs9FQO6^Q%vh zd1mz@JxVC_CdG?pmrocJyR;0{WT?9>2Lx>J=k05nKm=KXk^7CRE(%(96phl?ysPKY zr!$!P%|s&PNxyE=OMh%&r!;I=DnmbiLv zaDK14`nX(y-ee#tX!tE!?5s`$Cut5diw&@gz$(7rEAMVRILZ>K-NJk?!j2kqB=%9ML#Eb2MfG@@HMOBuT}5z8?M6Ft?M}l zgvEhp!`V-N7AW*rn%%J~Sa!wWMeap|t$jcIm$DcX7KFn`u2;*aN5Z?2I~a1)Ct6-B zovi1%I$n=~D!#NG#^~9%VvlTj9!KiSd+Z}qWJ(*iaBqdAVVQpDs`3MdFa7>#!#btN zgTDH$t77T)vjUn5No{TIZGR7Dv;O6iCdg@#ptKB_HE;+tizT|3fmhq}C_=Efb7CX} zwywoESk4Es$AiEd|0KY%2#sk^gTmZK+HnVZL#+e)3*L|gPrs(#Db94BD{Fb#G3o;q zJF|x7?loPFVmy_1=$YWZ`6poam8D6zo8gXsc5G)-_otoK7td6Dy=j&aIl-6eFO?#J zre`S6SVQLpJX$qLhMk|xheRs7B2h7O{!2%MVR6!vNKEFyjgH|#^X3jf)P;;LYJ%cuC9OcmGt3h@X^Z| zLx0Qaqt_*h&v(-#rc{bMzsQ_8&{{BAA^_nT({{b^ED?HEnUK(O*)x&6pkH7Z&I`@iMu z|6yUgTsg46$!y|DDjTkB)V&^5XjKqN5Z(0%iE82MfdKN}-})M5GCTUN9k#QICrf@f7Sxz@bcyo6g=E(EQW_&onVs~W^TVpieuko$i+`k=qcl$ z`o-6bD6@<+pR$*itRdZ(qU%(%iDh}Vz`gBAGYb3ZNl;T_z%=cqAj5EmnTH~o7CK>yCUyoE&0@bzu(~Z|C0VWvdzd6L= z=;z1~De01Y;S8%Td8(jrGKs8P^cnQxQ(}*Q2Kzw zJ1X82sBmINtBmxBY})a!jo1Y!3?4W?p&}v)8~TtG{tj{~!Q*}(`~Y5Olvq&72N_>P zoa2N{0+ZP-7HfX&2ZP5176jB_pQp9y0Uc*u@JG>kW6cho^}QdpQReOgXwH>ESV#Wg zv(Nj%vvM?SYRZ!q`$j3>>eRV6GKP*^$Z<2;C~91h<{zDrgGflea|VA4*yl_)xCmNF zz#-V}t9hHimcngj^HkJ@kO3ALJl*=sR~JRcn0T+9{q54587&Of9I$;n@MAqRYe{66 z3m}6hGr89)F!-XWoy_wdtaBr!#|`A4;E8acPK|#L9zG?<7VSG!ZyCXIDKq9cEB0liX+z(zeKkqDw7rk$58>!>5rcxfU=JJe>a6ZmnjZc0lO&l|D z<1emX%{iNGv%w_Xz}8MTuN7hAvQ+_}uWXxM1NO-?py~%`(K#N3&bN@viD3QdQ%+f( zK6lH7LS^z#s4f(D4VInv;K7TL)Mr;Syb`DLv;PvmL6x1ZRAyB7dZRm3bQ|g>65|sv zqJPe)D&Mcpc3C|wkB5tU&))a``6N#vD$o03GQV4Ssvo0DZ-FaOUby4Nnk2>!B(*|f z9Pe3GbzY(-aYOHf+4Rrk%%&s&gzm_1lWC*d^cm!pMxy6mSXw@eHU#-?t}hRCDc^-I zKw5HmN%8QW@yNWw^zY{AHe08il(2FdwpvexjpmAa@ z(tw+o{u`b?%%x-qo4H6%d*~`HR5JLgVk&+jDY5t*`uc(IoWpwwFi-cUL-2!b0-?H^ zaN*kdwHuUdb8AG5JfSUXL zq|G_G&zXRNasP^?er_M)K(E${4+A3Mx)&46S|CeuI21&MN++wSisp0tKLTeBD*j$n z%7>^{`(v0aBM)}8>m3$D!_kk?9|p)x&lrLJJ$f;p4d$h}=@q20D^qD3Ynv1f-r-Q< zr3>Z6Xp`()AOK?OlY=w(qH^Q{MTI}*wW#EX5~A`LFS~#s>h%@MTLD8Gu)q)8n4Msr zMxch-e?>{MWC_q&T^V;@NG>x?zLj$9Xob|X`6Z@!Uv0T7(Prmr8*+ZbbiQAS8+@^$Mlm zOZjX4jxva@id1IYgWaI>?z)0=cQkQ>xcAJQ>md*aC6g?J6r!;dV-yCFFn%;+V|5+%su zmT=jRi$NG+Z-5}+oh=^P&(TxqwL5UhynmC~_5VwE`9Eh-E%8GNUqj6Ef6tR*RP@-!<+gXt-S(w{k@PRE@}xTpu!_B?-KH_4EFE0UY< z^P=i*@fl(QFW z@i7A;Xc9s}@v43!R*@!9p+8RK` zSY20XeF8@W1Ta7CogW*YKmc~&k%0deVxKnF8S0Yr6Qv4AjPccDvt}l-R{+2g zzdx0P&ZQb=!Z6upAqRzVtLvD<(Rz_L3 ziLk<9^4bu9Xsh>4SHMABv$+HHe-3=GHLHQohb3}Ld?xc6OR`9xIb*20U$759Dpo76meb$J>}(jlJc-h#rK zd5>=^VyMQtm*x#lDys;I4CT=}W$HGAx#$V7b9#XoC=x&90AM)P>5iJ21eDN_sMcrRfTI-a58liA)8n*=yzIp1^uZ8-I8>^? zB^slRs<2a}7<2pjd>}**Vjmk zfu*~RPNhTA%V>O#ax9=rqZ#WQ5Xq~Rq4~n<%iHqeNALR^jJmkJqP~&s;BJ~-b2u$% zuV4ZIjZV7CpL{PDS&lz(eG^UCR@SE9cPRuDr(%+4sXuz&0|BTa{^IxW6_gW=dD2KT zY7!r*gy+y8Bng=Ngp}H4oBe)5oSRs<{jGqcdsu;V)6%mc)ujG}zW@e#ndfk3>%@p~ zNxWyfG-v(-UFWh)f~??65wyseeO>WKqMAk|m7=km*@nXj$=0B9K6p3KTThrWduCGl zU1C%g0H9K5h$H-du;c@a7b0!Va%HTX$#cD`L9f+Fo61xV>yW-T zxC7L@`x-hm%aOl_4`Ue3?pp=FUh8cAHnx1yW%V4Wgj4z%;ZN<=R6k^992SS}-XXy~ ze;FvN{2m$IzW|a>jeZGJm7B#7*N7E3GrHnic7nr?D>Z$D>}$-iy}0rRAPF<+hW%v_ zN0gupj{w*I$JSMbRn>HD;Lso?B_SNTK^g%`>F(|nkPb;HX+%n-yBnmtOX+T;TRH^4 zO+4y*T_1nXX7795vu4fAniXfXLY}*cvSOOodlUg?l1Wz5TgE=xb@}u4`Y;~ zp^7R}Kc@AAuV^W%CuZx2hB$!{j9Yy_zPi;|)lN4R2Yr%dX?80)!JR>S4=}InuV3{ZUR#Du>?nYRQcIB=Lbb~W^ehp@KA_G&6vG-Cj`-H6)67?nRi0t#_az99In>!9^R zQfM9ed=#SKyEp72#MJ_=%Vp_lH=_B&1qog|ePCpe6E8x7cqo%AH+)H1!AT$BTYcY&$)iwezR;E(NN4Qfqbd);#cxRf;LpC;m>pchn9IqlS_WyTYcmJ{wMdrVGsMJ1q{CgzV$qD?XFkM*Ne>yea@x-5Pr=T zp=6UP>zWg{(ou||J+x`)`N;?VjfJj^s~I->@1;9^`CG3e!Gm}n`E>g!CK@@iuNyYb zo1?ikKLg?TmWw`e@UnF@kg!R~v<>@V9duwTIKz;JK1h*qQI81a_GZ2!&^CT^_c6s# zaodayYby{;|@G zBUK8v!x^G;kO0ZTJ>!A%6-Y&l79z@x+R2{-7QbWNGhN~(-s|{*bczk)jiqs)fI)R^ zTI2W=`8P{)rLm$(Cri>NFU6U1N4rDc*-Q*n)TIHXb2H_?*J&@$*31Ve;3f-tixH+5 zrwQ3dT1z!Og_Wd@dF0&Y3hzJ8X|)0XKsWMhF-a1Mn=Qr{sd!lsvJ^|a;Bk3cD+ftGN*w|^HudL57MOuH5!t1EUeETmoF;6#I4+`am(T4J6 zd%|u38wA(k=yxyN-9Bf%mNFnSfmZG+^O#Qz9od%J#WXLlkbEoUPrzYz<+Gsge#y%) z;q;N^b9H8X(^&>U1@9lGf8Ifw^aLZVKT){vioq!DfY-e?qA4k`a4F=z>gT;$hONh4 zxoW?~Gd-z@fww?3&Nbmq1)dm|{_~Du_~jLxmnG&6%fdowt4Y~kI$42|ghOu%{ygzx zW-;Ho4iPOmnpkPi2hp;x#{@8zq0xWW^HY+AYEj(L8_BtC*@eNrJQ?c%nSY9l9{n<9 zPr1&1O>)L4#Ds?Sy)z)XZEIVsQm%GTB!HVs?e0qtL!n@6H@R&jA@lkSWp|!TKqjx6 zC3XP%+Hw*`JRo_v9lHS$}bsPe_$TV-~M(%(i3{tda%bTo4aXpzI*I} zG+dB}DB^Raym=&c58vQS@9wkr@L#?!dT-9F{*^U-P#%e#RqB{mfA1go%_*&9S_s#A z7C0O1`qh`B?ph6yWdi$LgXiO8HK}}ycdvOojeP%zXX6W5Q67ZqzL*BiwzDKW9ODqg z>F7ULL+-b72U?BH9;uE*v)i|4@j$SeSF!z`;k>3N&?FHisT6K&i@wY7IrwP@E^~wP zrSOsE5Q7Yr$D7@*QmvjI1JbWfQcqUziwQ!$a3Q&`tb1pM1OojaH;DZ_$N5gR#iDhn zPvCV?|ABAkziSr|b+F7pc$=+ffNXS{*G(EMCf=t2XZT{hO>G+P5Bz6)aud|ACYEw} zm5q$|j+IPvo2xTzo-CBV%4VeTeR~5%>=uZ~%R4N0BR)lADOj~!Mq=Z`GGlMoSTA10 z*gs=h4ZsgjKy&JY>=X|_-eIvDmD7n7G`Ce?lB*%T-iLt|c;9``{?Qx8JJ#;m-y*JM z;?1*e3{l3uY>m3yYlfhn{0F{DF~RR|OgYD5h(W%y=JpA&*XX7`EvR2RT^z^waaFYM zUh}}d)=#f`r%L3Nw>DA)&SmM20Z5rTkve9SFY6YmzlL9-18q<$I4(l;g4&4sUEw}u zGx^~9(`WZyRDS8r{Fv#u*1`qAceZv>KX*aMs0<2WGiq}ENUWy&+~~{i?c%l50f`Z6 zl6&~?=aziTNokGOX=?CJ7kbOwypY7 z9N+!aV-;SZVHf2g{hUR2G3t2ZD*-ZR^|`)}xOiZn-gq3UzV#KRp=LuJ2r-Qyddp42 zGjAuMI>D1hVb31~M3lFVCc35amYkT1gi+O^3iB~GS(BjWWbj(38-ivUhynP#7{7!T zl$p^ZKPJ^bgYj5uib%JPcB|#_^Pi?KedqaLdA9$Xz7%Em=Mgmlm)iTJz z^TTv*8NmN+VFGnQ7{;Gy4fZL@B{~(f39Pet&qk;MqiaZp2=YCCsGli+*Da_8xBB%y zI3p)(IX-4$=03xjb#9>MMbzJuDoES)18Z(~R2{;Z8%tYk3kr)+^*<8MvwxmD*~sH7 zS3aXH(!z+b`Q>9Iz^=HGuPw(5vs(V+9|^C&+Gq+Ub%^9Lbatb39ZvML$-;_GFQZ-Obt;Xv3w5=JM7(Y|Q?{mI|jWrf{5ZX*8TtZlLe zRZVb|lD*n3o8<2d!ohFF)@|9K@%seBTz{Dy$Mvo*^J}Y%K1*wJ^b52@Apg+tT^*u; zfo<_e@>z+vS89=UR~NO@hRp78Eme|JPkis9U076>GG?f}s52E^6n5qsBkL(4d>c)V zoQ9{Obm4kb2k&MO)P;Bi{D1I2dnc+mLagO9x;&Nl^Dz8rSeAMh&9eMAfA9aN;O_tL z=a?mi=AN1|UabS>(iUM~BGLOZW^t;@Mke)mQa^U*CxLelJu2x?TJS43cp)&2?@;($ z$Jr{=%svk?5&c&^nA`#4=T#a&Wu@+h%%6;6of)EM1R!x4PkIE0k1w3xY$#AD)lmfL z1BJ~&KHYmJLS@_P7f;?IhUq}jrsDGW*KYx;t*0?7E#5~4-G5T>Az2a$cC}G% zNw+-OaAIllsjRW&yQMiBrV=0{G`sm-LjIA^Z$2(w;N1)1q^Nv#eH07D^huiNJD0#>y23=&$7pzaLe-%46YB}nqJ}UwwY{f;Zv$J0G zFmI}b=2lhq!l-f~^XtG0a}mQ(GUDaCxR($@?7n8zJ$_1l-nQsq)E$ob193};smmlx z**KsJw(;NnG3WGV7t1UZdAwohj$@+!3i!h||36AP-INvxow9}II%duRi5_UJ_BG-& zU21EzLtSG_I%~%Lss3>ckDEyQUw?eUP~Q}Cauz|_z39O@5aOai_ha__LlPVa2$6Vx za?_KfDc7a7j4>W;Mp@;orAZ2c9pcW)pV9OUtgr_pJgiG}9p>&8ptLQ>o|^fxVPRC| z-ncQc-YMf6e)v-Q!DU&gai2nZFX833fLe>J=up!QLQ|jlpjQf4^%&r?%qRadh9LDm zY#e-cs2n=1!t~c=*Z3GS9 zjp5(p@5kF&IJIF^9!PLa`f!KL8PXaVL{G9U&*9CDc;T|~tBPTaGEt_$@Tw2t>!CBIK?P7#>kKyPQX}m<&!||5^S}GFcD)8(aT+p{~ zmmA->Izvj)SI$e^=hng;OVR{}atUz!j4i!ju_>YKaT!ON9NN=BX8FlLUM+L+CJyz- zM=;cRxBYlj*p1ftEC#cZ!Yj5dv$oI>%H;khY`gYe>|^gAkH3-m$@fzSINo}zWWU}} zn@ZSEk5ZmqIK>O97pV{Ur(1BZ6pMwzGP;dY$`M0%5CtP0PD9<_e#(2 zt}^9$awJv7cC7wi`G#bgd1Lk}QrhoE?l0rP@OsO_Dl-dwCqmG=2Z`=;>De;P%Wr*S zEgD^^0)q^IAZtQD4Z_JSX9P*S%4kvob7F#+rp6>?dL{c8nx6yBaUU2bt|Q99=0|Bg znBM?%c&>mU)i0C6m52B~(yU)k5NE?c5krifZco;i_Jia6c>F=SGbm0R03jOfFXw(FX`1Sn*FXm7|LCI`TMJ;IT;H{ z5=w;(cm=P9jXj5Y4r=`1#L>L{kQ^{nIu*RfRrWe)APH4^GLLdmD^GwqVmxos^mHoX z-E7zBBjf)ts>WNDncTx!T7#zf{GD{Mn_6Yk`+zDcvU@EDM#MOXajfLihOP8A>=**Q zsQiy-1UP>gUnphNR_w>;YAe@Gh>5QX=nQbhA7%(@S zyDlk%1QD5xxaqIo!gO;Tl28v?8*Edx=ui!6;)3v-U-bk=!;8&~b8K8@6HRehV5YkUg-+R-uH z{~1oBI`ejouM*NdDJsc{2RG!B;Nq6je=Ojaj%-IeoO@?)(1k5{4+?Qxkt(`Bz)DUK}r<}83b%o1pHsA zIFBT(_penpLp41ZhRSGxJbugq9-B`nj>Go0+&HFUP3SR**c70|Zyuqy_Bcj|@VQe5=h@2?pavZVGajN|+H z{I8M+Jw@cl4rg%%b6~n0A4n66GG|}+3;t5#!TSN~O>Mnzq5ij?Zs8VLT$WRIL7ith z{V}hD<)rn3i5-(r_rL+~hJ;~H6_9e_IQxQDfJ8!{ge>@Qc=mVM`#k34%CdOjae|7{ z${o%}%IB3E@Xs#n;NG=|BZ=|}z6&CA==ced$pIaaX!-=h4GXa3nTArEXT11hPzcg) z75Qx7@T2CHx*mfZJVfft?~U#El!HFti7_*)6k+ZT5~E2?1nbMEgax6It`n!g=Pc%x zb2EcMh0?0sa1@g^#ATzXvfj2o`^q>;IH^?;U*S11J)=TG0E22j6j#84o6V!Ouxt)9 zKxEg&I_~J{V{8h78=%HxUiJSdXK(>MYB^7?ROkMrX|BnvkpsU_+IhGjL3Y6UuZMFK zKzaW&7r3xoLibQ{Nb~ou2Fv0EM^%3*mwTnRjMPkq42$=CnxG!iJ(n)t08LXy|F>2) zH2c@3dq2=$%14a~zZqh-7MKucy=)-DBpyPV&aS{58ra5yy39woXW7!b zU`MRvBPPnp{G=Lql$|*sxdx=dBxC-||JaPPbE8mN#5GFoqe01kVM8tab*)s@*4R2dr zW9^+AkS2fo{cOZMn!V$jY@_}43@;2A>iD-OY#Bb`HB8aOmm(pu-US85*7Nu3{jFD0>+=ZF{zlP`T*Si-$lJX-TYoL#~=G=apTHJ{} zTZEHD?|+G^oKZtom&E|ayQ}E+ck!%dpD#uD!O=@ey(<&8$X;~&QikQD}!_{MUGRonLdO>^?q>>2pDKdvD(})6I4yUs* zr0fIZ$SK>%!Gkvn*N~oV2|HM)scC2sU$BE0WRMC{2+Y1MfT37f>Lk-LpYfIF!Bja9Q((seo?(UmS(*{|=HUjzC@;>)F{kt&r9m=&;AAm z8_v+0EraK;n#XVyHS4|~!k?7kfuT3qj!0`e(-vDZNc~Nz!f$>dUUhHGfwI95#(adH zOe#+0%zKPVv0UWitoqz0$At5e7-Il?YJ;QQU4d<;1n3s$FxVi=4qlg#Z}ZBIY`xvN z?cTHOrt_>;itv07oMV{OVn4gAWb@3K>jw4qzODWat#$0dAC!i}1#juP1Qa!a34Uc~ zFeFt$)!`ixrF0tjimYdY%B6kE6lP5Ns+`E@f%7jxC(WHxg!vt$M*beingcp)1g$OO zb|_?*)O`ohfdXJqcC@gAlNHXl^}$6qUkpRsiroM&I;~G^r0EHvI$=K`fM6G<^Gy|k zf|pU+Iz*}|GM}GLkumzgZ$zvFsG62tekyt7ynR*h&+!q#ibgG925-1=e>{Jh_d9a9 zz*2r-A^p&UgACyOBabS{H7@;_=~UQM#q=z$e&Jg_B2J-%r;PK$mH{;oJ-sx)G^*uM?bhe1GbwU0WZ6gi~EFXHj#KkI5 zLY~CUy*Vzm`G?F3jtTxI=Qtaz((n9y*(XcVw|=bw?q7g|M+xzozFrLjQKK`@@i}L1 z$4ht`(%cVyOgX~rn4hTvBJYk?kS2Gt3;4S`-CIV!;@~`?8$+XNZzXizdDe{r{?{{q zRqjxR&2ft36?NIEI5EFebIyk{=zKnc8?F1)$6o9C-E00kI8ioM+VwFOlYrAo*x9qC z^b(&K>?YZ_S@duCU(9 zMB|+fWfDxP)a?5Hb1aI!mQGutj8{dPG)!vb7$9K1vMtZB;KFM{?U>_DUk$liKpycY z1R(hz7xApE812^Bz@opme7jKv7s7i(q(U=@hA9nr$Y_UEe6OqDD+;b~>A3G^NW*wB z2By!cGKu&j%3b9j02K+Kf=7qN@l@6XGM|={$u1${g0~YOF_?}Zeq`P15?PL^n=~flB1TQ0A#h0yX82`OV zTTKYjeWZHV?yy+`-iEt}9`VP`7p2d5=#VydD;Cp}URi6$aJY{B!(5Rb^%29*p-D@84e?wGoSm=H$wlZsH%lguvUVyZ- zI?yQ5B__Z7ZTIufuz>T#U#Q?q1q8#$P-$M#T0D1!&5p8P@aI%p8KbA-sT1D;{NmIj z6zVtO*0vvKp~?_bG}+=1LYX04Um|@b5@^4*LcYfz$wK@Gzb@@H5uPN{7_)!}^Uldv zKl2}UTmRtSMQE#Qds{GXB_eba81}+!&!~96kC_aPBPi64=q&x-$^VG|GA1Uxb15}o ziiVUmnj|I`+x$|J*PKDE%13(y|HBq8z<eBaAFMrEGhYg-3Zz zYiv7Zcl8$+j}z?35l3uF}0@2zqjJwsUnwCZCc|KC*m#SG&Df$U2Ra+*>$gzkDizEO{)p~ zqd!qe#T=*fc9_O`j!J#R11}BGYw}%jKDYM<1YXFk(E!&A+>Q}A{8)pRPr5Mp7*jB? zOxVLsor);pU%w!LUh^G?0Pq*ps(vZq|B5*9+eb-b};)|QD^ zn~1`Mgc4Q7#AY{qxLuDtJuB_#`t9_zr2^@$Bxf-}qzxnE*>Dhu!G_XSb*s(ci@%>s z&~4l(WVS8{@5w!WY6apy{O8YAC2t7gyf~y;K$g3h6yM4${r75+?!rIhhZu+8ny{GQ zdE%FEpQR39m5JTybHOAW%f{v*ckg+`pY+UMU~AbGmF;aq)bO`O@vKh4y@a79(l<}_ ztIz}Omw+7q;wvN7F-y#yt)bg1P3UF+TQlBW59yrHR%|zT1AaXK{}d!Nodd}Xy~j!0 zVOaT1QtKAFwsA5G2htD&auiuE?>+uL?A|~4P0;KivIBSuVujILwDaVx2HjY-|KT44 zs(jHq{DrlS(H53UI(nfWf|hxs^(76OQ=7CAl5yPKd;adzHmML;4n31@u4V(B$ZZZd z{U_0t>adBf){}5$a=~^22&O26Jj!=x5#IH0t_AHQ(C=2kVeN0H8?wG1uNnhG zSejjqs6Bl}Ye-rN(yMF`q&ziU})h_-BPd?SR;zom%{{(*DUuV%1Y$%UAU zh#bx@gdpC?OS?dRA&T9hRpqy#edZJe25IjWo02KypkfvqPMV+*jFk%Q+1u$q4O!w5 zFYiuZwgH2tFkbYV2E?!hGv9g@u#D?|#V*u$0kzp$dLnXU$;ez-UD_jZz^$#TV`vMUTp2()e9seNeV>xll3o=?`| z9(;8+Uf1@>IxTXC!G{S8)wA;;s6ck|hK_=NcfT0l>G?h9Y|inTeDyH}8|vj&7VnD# zp`DExg?y2@I7d`jo5vFXU!GfgW!*p+H`YP-N!2gOIy5NZ@mFRvW|tgF4oh2oMXw$? z@6K*Jf~*vTjKdZZ(x8(;rtl^mSV+}5&fi@buQE5Y1ib0&*`jGlVYgTipT%-UJoyFB zo;v)9>Vg!`K_W3!pyqqvJTEU$_Aj_B#Ktaq|)6-CbwBLLJX;s!uH~Z4D;&6k9sDR>@wtal_J9Z$-7J=PvF>Q{$_nK9Go|0RCS5-ID z6r@jR{DscqG7F{Qk#q0pziHg$Msd`5OZa4>(ehsYSzeLWRcV@mh3!{=JRxWOM0>K$ z(5mI;>E}P>tdmdj@T@*uH~){G zn@&S&_Z)lJvg?X?rRIoM5qv!7`eB!5^Hw+FH0h<8J`}O(u$%jDdLd+-w{X-SBF{|@ zIH=JqgL24aGMFW0Dh|9KIroMsp*;+g*O6bYIu?$w#g3@jVpvWowP%KN6da_4+JYi} zjUXw2N?n`=PnF1^!zJ-%Upi_F*&mN4eedDj(ILBn3`It|>s5=F@0u@Zt z(@u+SZ0EO0F*_D)eak#TkORmELf*Cq!G&8kpISX~{+|Q&&bBlQidSNn+|0R+<8>FQ zJHeLaSOL`RtVf&t2UTQT-w)qG^Mii+3V zXQXyPC}M0X8~3E&DHY1pdq1OLktdwDZaN7ojZyk5rH~=kaho1F?_-fpq-u2kfTYb^ z{2ljLPF&D8qT`hlEL8bXqRR#gaBGBe49QFcPc->?U^LP4sUn(7d5N(W>Dtbul=a?F z({|lG=O9eSXp)bFPcb(NkH(8y^ZVPd0*7zct9^-RI6(%91*c#TfB$GJ8Cxi59j&Bz z>gX%bj-t+|D<1?mFqlS4$5@h|VqL^vz z>amX;RFf6Yf3`)I{KE;H3nm^~J!-g->jYySIkixLnd<$|_qL_(llPIi!bULSIumU` zsJ5LuU2pb3wxt^{68Imd5+X51$15Y^>)(P4zNeIExD_CJ<=A{@#5`o5R4I9FX*f*cm9u-~k@ZstmP-DHdzN*@J2MJh`5A|+6?TGG2 zZMp&fg)ZJr1RHqjPlZn~u4Z5HS~xhEO?Af#ZT~~M+Zo(-ocj9-4(U-W_C7UOR8(51 zq|IJXwn_B69HE1a|0O+naGK-kM(8QuMsGbrQx#C?y`telxq4WF4+I7@{#n)No$AjdIpev30rQU`5W7>`&IZb z&Lv^~^wKmy?Iltm$mN$&Q%@Z}Cap%9CX0o2m8&AQ&60m6O*|4jW@fW7>VtJD2`w@; zc3i>>{MZ@_`hEh0iR!QT(z)UNp#5lD^WV^q;1~ zlkvD69!6W^Zn*NY1wp)Mj`!>Nb0Ky|x~v?$s{fByZ7RiDIuj18L?R3mz%}LFzuy@S z$S6*HW`_{S7SBwBieDgVK6%*e;mm37)Q%IAZ|tAg=k6Z->qw0eqZyGKOtiPzkn78N z?Km7Tei%bzTG9u79eLoVGHe7CF$4K$r{yld!Lo&-=rM!(#+PaIY|vzICAZq`&S71v zA4xxlx`OpgqOcct*p3Y=6*&KCt7O^K#{RtDJ0V`ZI04uLL0MuCJGH7W(2j9^-Qnh{ z36Y-9t^-f9*5jA0S+U{qKalR=PA`ukq>R*Wbv^r@k*^IEBpoTcOF+Y@Y!J z$znw<)OkWz51_c8mhcD*>YCKMbC|KDqwXf*lSIgW2ZL5$$sQqM2fe6Kf`I#(Wgr|y z@1ac+mvGXU-0nHf43qsxdJ}`^9T|6y(mKIS>s9^sl^Zy|o^)UL! zYq{dQPLbWviW#1CT+Q`=Ztu}U=RM&qoMQ3ZG!$)6(Hg8bP39_1wGSriYnUxUubzLu zn^J#Fx~2;#L*#78XFU>L+)>oN>1l-(of@_tW30<_N>R*tKEN9nFZzinP0xinc8l2g z>y`+`i-jMP*ICCi?w`zFQz1n^a$Y;o@QTLbE3@kYDx{|d=CEX+Yy9>y4$tp0zx^j4 z#SnqD;pvd-5#lS(L;UB_MIZ}`^4yM2j0QAvIax~$i#6D(2kSzaP_E@H;b+1s{dMnkTcJ)j3G=z)b;)NIL+YqL9kgj9&x!NakW6JSiidLyE$WyW#~=!ggtiN6 zqx)H*uZ<7plWfB8&?CR2t=zt?0{LD3MnOuYRuTSNc?tS)#5Q`NMLw$9IeUp&_R1|e z-v}QRGpI`ei&vGgR&dC@0xN>Y#o#j?6_lEzqL@!<{rP7Bz{8(m634w!m$aPOUM-Qw zGl&eKh5G6^$o_bPOtGd4%8nBZl~oz(U+kEzZ1WB=x>B$2M0k($h>=#op`^pwES_9& z<$5O2oWY z!cE@~$58~VpIG8?F|3|pXTJ>TYX{2jgbC^G`tFXJ<$GLm@kfyxT{=;4kv>BZ^b8#( zLi~vMs7=*My#~RVBg-ORlqi!S-CpcxF3T;5UH%D047r)yhuwmSn;OBp z9`Yoht$x646s2O+eO*@Nxq!Ru_XncFlNSZ`TX4IS$%bFXS(${LT}{}ocsfoIAC$W3 z%Y6t0L(R5%opoQ7K-898VREb+^Ac^odFU!^yBP_z)ST9GVF2ZirO&H% zcq?%K&N}s9$Afgy<43w-;trPGHDA z;i^eD)tOW4*-RhU`##H1-x^Wr$0JXEr%cLE4FO?DKlFK5J^wO*~) zjZv9G5hHE{h@)aT+}J|H|jM#_Cb%7+WD{5-O@!BaIv^0L{NqB7> zknw^+h@alvs8(@6y$%da6C&gIgvSyuF`V0#WhtcNtl6DImeROOYEaY_?y-!i!FJZO)zPF6%+6T&>doEGlWYepYeibi{R!8)7Uxg~E* zTu|*AP@K>zMfZv<7)SaiDOpoqw$dw3;v72CS}+;dCReb;YQ_Fx)?q=@Ot&pij^clE zq-Ynfn5q?A?j%Y$7y*1_7_YTQE}@9gyw|e(#lE^?3v25{b(~Lmp zzRKGf(m9)7ml)pCx07%QCEE!GU{GXSPJeUyQOgS)@0Jfsz$tvAU7;+IhCo3}b%>wj z=05BCcz>1B!LK4wfK0i4;Dt^!wVEGs|`n|d3lhHB|nVGt7Doc zg_D6nF1ugo=il3}fbmIw!ulTu3=Ng(7_j!X7A&;(Bds*g0rp$>fNLI+ATY|K#9Y>8 ze03jc80w0^S4tRe(l}beLh?bAQkL5I+7!?E5nmSfLR)g?J!4>ghRtC^;MJMlHhEnT zes>9Wx6VCWU!`~~HFMG`6+2tp2)XN44n;*RCiT$qoB4s&oXosL)qs=}ZPs+Igqx+; z5(Dy-URoOOX20PPpfE?>mU|=a>2y6_U%hbR-%W$E(1xcq7QnBC-G5KiMB}23BG9Sp z)2P~-?gjME;z~n>NtsIVGWDab7H1}9dOOD=N=hOz{%Uk)#diAZgEAepf@DS#M|52z zAWf@b)u(Ws+97rEYLa@OmCT>uGuDDZ&YkvbR=k^f8oM}*^Rd4qSxCQGL>O;oCdUTO zSigDg1_m{EMeSx>uI>68X*DpmovrX$i9suOSbP**Mb4}t7*KrRNzSRergcZ0foj&? zCy-^BJbWQCT&M}w)kuVNIQ!X5>)q-6&wjuZtFCpp(itu?H>FO@oWmVoO@r+1L{r@RXmr1Bs0V?Bbq0u<^~lN)*zL`Nk$N!6vMIqzKnmMN0)9UHl~0 z1GBLw1oyy`B#kY$^P!$iJWpUW@)?}41J7Arm|@jtyaFFZ@_^MYFi4N3YgsL2cWoc< z(}GC)rz~VhxF?-lnCSW7-t1ZgQcN(YZIZ}*?>WP>K(m+NFbU>du#dX@H$2cg!XhLT zBFq-O2cEL!=F0@5Lj!M+Ag?qN*og;;R48nyhi^DW)8%x}vhV9m{ox6|lZPV01UH>J zKfr0TN%H8|)#Yu$#S$lChe+&iIf5P_`dj0hz8N&#Pn0wX|KtQ%3bmtx+j-^P7m4~x z>(Qc=1&<3JSf~~01uLt24054EH1br>@ro9jAFt4 z>Q$xr2?hzJH@|j9I`;B?f1;5+jIhryL~9gjv0^q}AEn+&T;f&ylPqnRgIWZ2c;qxM=G2?;26 zy1M3{{Lj2w<8{#e4Rlk?1q@#|pTozB1 zc#EkI>@VpC>MdL?Ksg9Odr~RLdiBGzgW_e#;jXk<)f}S?n^$MRX|5{2z zQ=1MyF>ZfyoPAEyW5=QFg4F(Xb?fJG_k9sD}1xy^ke zFkD?c1R!z!mDnieT*jlqu!6E!1qdFl)J$$^U zKtU+Zo62Ly98VQa@#{%GRr8b8FT!@58~#rZQPfRijjgmF6>4s=DR?iLmJFuUCoiaYZO(@-8KNYWvaErM5 z`Jhmab1NOc(d>Gk^h+UlAZY6-aG-$Y@;m}Ab$7fA#zObOAgXxhFX! z=s9GDG{L3xB?X72cGc8QDqxU^)iT?4NrEEY7;-MFHdJSHw*6q>Z;cWhNsfI8ctN-a zqV{vI_$yn|CYs-LU+4tbXAM=)gjnG~84kg&?Q+k2cz@?${4?={qJoOQywEgz*?dc1 zdc`(m@bxR-n1<%BLo&)UG6jhmC}J}+dl9IMp2IDcw>b{_V$jTsd^TBUxfF0V)@6N! zwlI%8ZDRz%4e@wO>Z4UdCNE6Kgl=0xcMYCNuG@~KyKnvm_FymuEbcOeAslo+yL(+Q z6K(i7-x}*}*C|srL{-T%i!wd%v?kVIUW4J_(h>o|?N97tSWXTji)7V2CB{^t8~v#q zn0O*XZ7C%jFVM9GKYV+=zdd7GKcIK$lQ*A`dxpcWvvmaq6<^>ay4Zhb;SY|hBW*1w z#D168JGO@G-T(V*og|A~!~;*;mAJZ}snCQ(lyvKfkY)9mt0A>o32OXbmrlJniG+dv zXIFs#Myp~+SMF)qmoNiLFB}R`wD<4J-I3MnUV1bq8-)wNI|?5&WGKn&Nue((_MY?i zrE%%6SHh76@X#*3!^^k_-yde38D8S-^H69?%j2giN)a-nR420m`0e62+4K!VtL#Vv zKoP8745cw^NdJnpauct-Ch#EOjaxwRa@H+PB zkzVQS1zqQC1W-a4%1)U4w-0~V6|{cnaBi0NuC176Cdf=GbE;KWIc2rXJ-D>x4!@{T z1v-n`Q`?o+bvw3YYF4Zj&0-gY=R`TQykz?1CAWRKqne~F}{mGhJ{ zdi8b+@2TjQ7uU5zrdUNKkZ3@qGi6##8Zi5r0iGv4H21()oZ;Wi^@H5oCGe_~efj4;^3J{-Y_7xK?aWTt{fRn?jC zc-*c*dCAR4BQoA0G|0(YRS!JT{P;F~FcjrN4R>Llj`m@8PyLMva#DE2@=Z$eM~+1u zFqCB9z9M$-_a8pY%R`$gtdxn9%4rZrR77##=TgNIU!{YgQsh^-8^V#;#m~QjU7v}$ z468*HS9xmnVSK~Ac;z|C^uQBniW1+Z9$M|>XnIB>h_Bwt$F-B>nH>DoOFmGnh}-|XXYkn(C~?*WVXHj!c(u07(l#z;Z z4#b|o5bn4&JF5}xIpIoc3o+>5VZszid$Z0?Dlw7SBC1eU20~7<68L z5Sl< z*L8y24Lc}hlKa^%jO&zC{luD*KfIq)EpKlX3^y9PJn$rj1GCS#$82z-qqdd+qqzRY zl!tZ9X}yQn=T@2lqs8zan!lM{Qx}BXpjjAtCR~i3U9C|p-l9t>yu=% z+GWG8Mf98^`4Eu*W$t53z9x`sZsS_Vu4Z+`~$5xXi0>+G$`rN=YZ(F6`UCTk1pKGrom`IaeJnFz9LLREb~?{Vil}B0UJ1o7|3fQMUR$R7SogCGxHNd-?~G zpx!%-++Pdd`A$=4z=?vymsd~@`Vu$1Fg1|w#uI^sY{HOtRNXlOqmbw92(wwTi!bb* z4a5cG1!>D%|K8k)*@)$@a2+}+QC3ZUf;OY>=GQ*z=X8oS?Xs8GExFFWd&xtppSjKR zP+NzSM(>vqmigur$Mn2N$NO`V?;sFjyPc418i6ousnVK~0#4iE0ob=>o`W0;5|wn70|^)WrI=EYFK$4pEPf&E zgLfPvIw)Hj&Bi|)pPnE5N)*ve;~Flv6(%fbeM*Zyiu2wCKM1I@bRa^2Sjygj)#max zN22ph2#q!ZMB8GS2C2appZA17D2sUl=csyZa>mm0m^@1TMQ~tDf*o<=d1T4!XD6vs zccFkYC|QEEAC;krHt62-KR3i3%H~8Tj1no>V3H~DDx_Vz7Z3zfME&3RpneUOZIqeG zEE@Mwd;2zE=bHZ1HL$Qe=03>pegxGE$19i`2eW>Ij(gxr&%p0(Sl|9Lf?QFu$K&s@ zuyFbJykBTfws;(x(u=;U98X(U`QdzQf$(<(`IoI;QU=BeBRE51u&(KGh>Tg4h!nBH zh0JDSjokwN7Gf$lR*vzM@`LeX=Ap&pfjc!1|TfG;OvA?!K z#Fx)~Q_^)04suHSt*Uh+!|4dBdyas*+jlC;=0L<( zbGp3&1;kx`=wN>LM9qe5Ns^isX|7vCblv27xhl?w@Baw++HYHRzXeviTw_jlLbldH`}u2h#qFkw=Xfhr;+RoB2)Rt35x+0U4Ew448%uQ*S~IVhQ#d@vcr#AIhkQ zP?wy3^>+jri36xUeZ>12h|}MK6*g@QmlxO1Q2*LVAuYg{Ni-G&?8|4P@oq;ZxtR*M z5+%e5M{%Q14W*iPkqV=l@l2%K6Mzv6&d5Zr2(d!^s&h34j#oQ;W|ti9p%|5s&&txs zm47jIFW~Ot-|?Yyrm&ni>>$?pg6*!qT+(t&e2f-Ym=00Q@}Gd@^X5-4hWud}#zm9A zyyLG!RK@uEkAVC{9~WvM=IHiik-q%co{^p$^w9`)t+0{^W;#VWNYeN#Aa>SwbsoNN zmwoa&dm+z{-s_Lo*^evMk6Ux)UlRu9(gBViyHua+Kzy0Blq@V4uM{>B;(!BB_ntHx zv9t7Jn_FsN1pU}TMI;@wFoSDBsYS+6mESUFQs7-&T;ccZfB#HB;l3Bp?b$l#fAB+I zevJ2ruOA6xxYZTwnnD}3-UaS@qUZloU_eBs7(14^Y%9f~!TJ=U05hh(8zidWnok^}Nn80FOW%%}tLtMxD?n4hMssR7O3J>&4Q)Wsu9eG%&#?hjF zs9GVB>nSV*eeuN2ArvvPbyAsRxm7Xa<_M}#84IdyYntZdCxMJnBkk73VdZ9E1S5aZ zj#w+RuhzJpH0qvf!*V9uPJPN7;<5|ZAAr%tn7R(>XdA}frTf`enPF{^F6H0+BcT2=yOA>V2(w5xo~u}= znZlUcdheD0&=)XEb^uRo6l{9O4rD>O{}_Z9ML205wjJ4$qc8 z5a|Jfr>FxKTB+V~j#Ylj+mf*@qhsg>ictq)NQFWrO#}T-p*9$8Ho280k7dx1CtQsV z@Z{jhq>7BB)D(9%4r5cFLfs1}PyFRR=H*U6>Bd-&FA*mLB#PeTgGscpyD2U})tP>M z)B9x4yC>A(379F_tcxz9l*73WN+&0*F@bKAjQ=eYCuw|mkj)Ffb;&2aP#JvbY)yqZ zzcP&qEh~n>D>X6Phkrecpg=Iz+_&$z z1zYRe|+XP4AMv?I1*E#ux= zSvT;Xep^uTNWx+n*3}1TD4ca`56f1QG@L-E0N(z9i9M9YW2gYIplIK;STtXd4lk-n zJqwzh5`#4@Tr4RX;*0N8&)a4f01Fy2=R-Fkd86nLGk-(;WLGldG)qG&CECq0DMK8k ztC0I3V2&N?-+LrM5cz5sxe@HK+IfNeJl-OsGx(fgpt9%d4>|x-^p~(WG_E8(t>-nlz%wDO>_s*Mo95DSzi{+3ET4fs2aa#BTvT9;%%mJj~muN7fYowr|Ap zw9gn~p|K9i6u7lv&iY7ES+r3%t^?K7CaTKaa;AH^)!=fVJstOdL1w;Ur~+7;LE!)` z2YeO9(F}RF%Q@yE>=6_9j&lptA6^3bEg(0i)>5(+;|6KX=C}wnT)0`sn2b_LEbo*h z*JR+896(WI!mGx&B^T%_u_~Am6uxmEY`Y6!5<9afV{XoSri3N|isC`3^XNrGcq4y& zpKuUW0(WH)uw^YaLxshr-Wo~b9rqyMO7!D>co=lMaW&VF@+-sLAzb2pq;^Yt$1sp) zmd(Rr2W(?!Q-&~igeav9_mp#eN_-d{wAa95`>%kjK35{MpaQ8`#V$cw(sj+Le3z$Tumo6UYF(%CGJX6aZl5)Suwzg+PL&V zXZU4Qs2V>(pycUfj9^+n`yo1AHOADI9{({25U?}9Zo$;}esSE^UM-4KBxED08UtGx zg!7B+%@4_Ndff*BH@i5D!L9*@PY#3Cpv=!n5X|8bFxx;IopcBDW^fP|QH-&2M$~$z}ZV@AQ z`Mq6oxO)cv?Sp{)s7Xni+_(%EbgzE4_FzMBeIz^y1ND~D{r!TT?&}5SYwo*G+o=Ju zP@&Re=-vP92ce!JslH50O7j-fHHWuYqrifwGETi^;iUtYik0F zdhMN_+$H*^ms6@P8gSb z^BPkbVmM`$&=lw*!M2h%7>G#t<)edHc*v!THLD2SWC73fnwG?q<4Au_;E7$RgLzgN z?{!AqnvF&?bKEV#r4CQpEeiH1bqaOW=U&oYU`WvYTMyCbLFW!l$Fw7(=n|V&HXMn8 zX{i`78v0?Nf6Jh+NfFA@a3ttef?Z5*P$d-*psAU|Kz&=xf33oP)Wk$zKSv_PAna&b zVjJw6xde0^`s-JSPLV_>?*~=!2?8UCs>X|+Rf1r@b$sOOj6p7(&1ZWJUI+bFfnu@9 z%$L--3C-XgF5XW2CoV<1M_HAnysLwq%2;q>vw#Ym>IZ5ZI7Z#m1@kwSpCqJu!Do1& z%@6*xFG}&Q8{Y<0K!ncF@8N`)>`gcZ)f$qKn-6Mf!a_Am5+BpRmTB@;cu>Jg!^L0t zM+mQNE=hJ2_v@`SO}t~D!uK%%FGZ7!F#Y|aJbFE&skZI&=jX$dT-O|PQ|RxM!$2pd zVGp;3!M-Ui-!EdsOfYa^!OiE>zwM_Jg*zJtnlJsmk4M$Yj~ziTrBnJCh zKz9jJ&D`?J>Z}AlX+sh0;(zXR+^TrnA=76trEkepH+9<$e^3ZGg4KI{-Lzci6MPj} z1YBgeVzS%^Q7Ox*lj~&lJ!cOv-ugB8@ z$V|nuNdc#^wjGC4ReEP4lL<-fb(jw!iD8*V*l$p**wlfo_YJF$we$H7L0E{AJ(jyQ z+ql%%f+OTGHi{Ov#w;N$FeKjB=om!Jp|JU37^?!``89OxtC(T8E5_hZlu({t!+;@i z-H{%$LIvs(K+IG6POk1eJvJu$v5)j@OOBVd+#VMd4C#-WY6^ZKd__jbL55&5OIR=$ zxuHaMc3c(SCRkkmNTHpVV zm-u{FdSt>#eyyK#${pw^zJr@fhX8#YHLap8XB*z^$gC&AVUgIWK zVj-FYK=xSAxE~x7$R%(Ccl3?OJ1I=PlZq1_hDJ=7R`hYnNTD%4hM%X|aBYsv??579*2`p6f75lBRav}hv|1kA-^4-)|3wya4G%g;c_nfI%hvzlWO?;J} z|Jj*$v`PU?!m|gOG=VQj>15}lISiRM<2bB5D7&wth{nvEGp4w#0ZhxBeN_lA{S$Of z9RlZlAf#4p_n^;bU3arML-Luun>}{~{l!G9^~w)Cplnufj-l$#-of12 zFm^z8pqiM6J5NIYGFP9-#DO@6LFa!|lX1$%H$|$ihM|OL6_b(MGca_@bvo^5+&`#^ z<=hg555a4?-*bi_3tnY4n@X&VU$T*u=8%pEZm+l)evYf;rnvtRGc~ZIWg;F_oJFV} z4aDX9r5kJL5FwP-Jx({M6R6>N{;VsK{eOh?G|Px9=360_5dZ#8^t37{#`0VY>%El3 zbgJSJ0{hV>cyaNEO*by)p&z-vys;mHx6f6aBr<|kmE*(ns#}6bC9@)-0WL;Ks18+n zq*YlX8ha8uyhh!>%@h{Vma#L>VM`Clr^Au^A2AKTd^K@XYc98iE}IOi7BF5_TdS4V z6Z%#9#N;JUhJf5Z3|}3*so+HQf|t{$q*ETDplF1ktW;lH$AcwrXc}|L0FG3izz!}~ zY5aK6UY3L7DkuRx{2HN?UF2-$hwHo?LWs|o{}EI17fR0OO_GeYFJCx3FKAB)jJ|9O zyv7m_D|A~)K0147(e>MOXl-*{6ubOYzi32s32SMm6mIW4k{dHrqvS-#3}xuXfx-`W z?;{=~R_Jk5I(DrX{PiKf*-MeFf{k}o>Gg;6uHfsV|BKV8 zi}~CbqbpK07k+I!OUdHw7vz{0Clz2u*t;Bk{dDO`DCc`ySq0HivvbVQi!r0fXT(6`(KV}mSNm4_(%HE0gf)^@?l_L5+o$77qQ=S3Gf-!+H0 ztv$PsrdlPnW~o0It#uu06dAmm`)A^ejWiREBm2po$GKpm{Bs6QH_S)>14NBYK&%Xr zABLVh6gf%ZeKz74Z?7w%BHvS{%KEsR81YDtxqwi`I#uV*u>*hy6K&;G_$V(=VWR0) zM4MzLmj)>53kmUyr6DEGV}p9Dz?inNhPu##r+2K-I08Q>NgQaRZypH8~ z_wnjcO`c@^BcOE)-6?$Pwi>s!ZdUFohvvM~_jKGfi+Stwz+oo46HEn}YRHwm1(##f zYsZyTs#Mlo?1rn3KKA9nfLdw*lJR#Ws7ab(txxCEB@}QME25ap$vJviPp1__t$lan zjvlCa|M4+-?2F{ZzL2`ZMr-}#;+NlkD9#1qY8XhRm*DTNQ{;jtzm8Zv;h_?gnyhb?Z+E4k98Pk z!p`n0E(+XTiPeaA%N6ViO`Q!W#2q+5g{fH(qRIr#S0haJXeX8fe^2jM9 zzp0l7BD5+eV@A~(eucp0cP8HpRrQDJ**~$o+8oZ#bD;IjeTQLnA}^a?v;B{S7tCb) zII@hH6O|%&P~@JdAP97n(Z-C4RdXDh@UO}8`8`4oUZG-H63O`AG)@7nTkTim*kI0- zWW;z}e!$4rjlP>3-C!cO8ol(h3Ld<20%MYMcH)IO!s}W2q!9l>atZFI%3=!QRl>># z%J75`HMAhGo_@zo56f_0Yz?`@iQIk3TuR%7NIgusuBEY3$Sb^kwyS5-P~;Yg1?BO_ za>-YpmZ;*nYP-K&1uP<+KwI!WFKZXQ0l{hJmy zz{6kWR*hVS{V4BIa;)u3F`4cf269$F0|R>qtQa9`Y}t~ptlpiTZ$-T=l2nR!jd|oI zB!PX1x6(R`NEGsWZG_Ss`iViV%lafTjV7a)sJ*J=|D3N1%X5CxV*2z_EkN6vMh!Ez z|5!fndORbqlKC0?b8`tm=f;fL#T3;TPrv-gP;XHC$)Lag38$1zBONV<{8>+N6L@#- zD6gsq@fo49&GbA!as#BPhu}WR%VMu%>?|Ux)JT^C77%Xl+VOA3%H+Mh?3{sq(67E) zes|LAW2~N+T4A6`pR~UMQtEgfe-fW>ZHL@1CJngesnN3mnrXnKii6|Eg9@0MCY@8v zjJP%Dq$Q{?m;Xdb(Mng#Sy9d(u zmw&duP&h?7j;-*$rTc9PJth3?!fGG^J;|OFb6X9*@3Ico1>j+RQya_G6H*5Px8*9= zM7{k`rT8P}#S`w_EMi%tCFk~b@Q2X2QBXQr{j18j29m1FAB%sTGQeYb0-h1==$-jxJrV+iKCy%Xy}a6IC_vq;IugMs8vBpe!iO1w z29OK!-)EcEJc4}%Y|RC7LfG}U{zskv|3P}3>fl>Y35uBOlFtL^XRo+86c!3;yK#?n z%;dbjiUwH2asrT6@#JcLIb#|a1unQPx)_AK-~U9vPArwW>!5C4q`V4-w5V*tI>XoA z>VLqQT=2CKLzUM^2|CBORpvdF7p*jSvtG{_D8^5$*}vfiI; zB8=8eB-FP=-E$#`lXdom3Q&E?p0_iVRJG3bK_&5NKY;-L?#xf_q_X_5Inx|Y4lnaW zAcS9{)b?trB?sO{L+*YEGMHTEl0HA)Xf`$NP8*t6LNo*m82aT}ik+S}#3vH&TJ@{q z7L?Kv#Vba9f>@ZaG6{dZhgI00nJ!yjme+C5o9Ki}*S<}^m2+Nl*5wQSTMLf3F%ysP zeoIi~+?+=*qxSte1_b@gas(CnaoJwiS$^O#yO21zeSHQlK!E<59UldwpxSL%^a|c@ z$kd|X_}e-mvcRdh9|1SxW2!Wnyd5>S zyGI_lz}rN)xX3Uk6oqjF|H3@#z|3Mp;t7X)!@5eqWqsoTMgDRaRMI}(3gcVyX}g8! zS{xH%wQZLjWyYkhxXireC6K;=c1Byb@z=1oj#$N0#(A!|PlCK{&^)m0_>+a3%$S!r z5)6qjAdG_NWyC;5uJ?3-vS18)?(KTM{>yQsQ>faFx+Yyy%ih?|0CW6Q6 z)Ky%S&A<0DAM%+=SJp!JX<0 z@M1LryDw-mE3|SWxX|eZlD8&AF3G#pTkqm4W#xi7Ag>LE)SP>ZustGmDT$sKw_rIT zKi*AMH#sTMfno8sMe@~8)JH9Oq#1SM2Riit*2=;tS*Ce1M;PWZT9tSBtQ}imNW!9#R+1_ zZBk3%12HD+0nCWVq4}Ykt=(cKJ)+)IfCVGj4SuIrW?ElY{p0C+&{HV(6ISUYJ?F*N?|Z5pxoDH9Z>yGro1u?PIBGwxO-8A;2#aB z+h(E=X&6}ptARe0;~`RAI`d_PH0TL*LgGATV}ZFiFzcZya$yuHuY7vl*5GtqFqk(PI)boQ{5!Kp?DIbTtaS)!`C7C;VoCA%b zq0aD>g&{R`_CH{=<~0e=r!Z(3h?xHL(3QMby6y1Gbp%7&!uS;*rsAq}YllHq|81j9 zda7S8ZV?GGeuEc7^yj70qn3Duk`wtM0%~Jm-DPEpJ3P?5lj(bDs#rj+n6=Or_a{Ji z0@ij{-^NbO^e3B;f$`xC#DxBrJ*;1I((8llHjM^90W?@>#NFM?606mXDROt!p*40Q z|BAy=Jlkb?9ql2){jA}E1`uJ7`=8(r7d4CaBP0uZyM9b#FBXOS^gI3nw}#IwKAI|T4i_Mz-QD?82DZyO(=KAa2y zmz)0~diY9O()Xm)I6q!@%g^*U#N?mg;8-9lS49#i-_%y%TLFz620Uxo=ggcz$2)vl z@hal=w$^!5)nB1`V}%k69Pe=frMtk{N1%|K8u-0=_w#v$OQtky3Pt=8(>wVAVWt5U z%ikx%C4zon#eyB#hzj=HFkP2KK=Q^e=xJaQffH&|C|2r+zpHGv4uLVK^|S3!iut=& zj#@VpN)s#p$am)XBf{4>oxdRyBkqEAMH2N612SFE^PuPIp0kCmWB$>A&y_|w1m&6C zG$JXBvd8%oKAUO1z0ajUoOsnSi^N6$8F-Nw!e+pA=n_v%UHarOhEt%V5{gD#=@a2} z)C?aiA&nzoz`luXmE;hSOXKT>nRb&e%v}R$Rp(X>mcH$uU>ADVn;r~ES_k(a9@tBt zNS1AACptp(-QBw8iXn1Vsl$+kf!;iOtln_rbjGHDx zV~{7#n&E2#nYiy&Hn%CG_L5=z9?zJ?SInsmq``Xp?KEZq)0&97q*R(6Em{J%Nwo6T zlY*<}6^ym!5Pk2~$$&si6#1TN(Y&sQ!p0ymO~DW{am3aFcGc_VP2lm^LdZ4H4-h)w z$)*BL9|c7-m2^q~WPR_8&JeWcw2e>{T$Y47ZhYoo0igp3ANB|j%X2&2_h0!Bv{u4i zkJZvn)OeV}sIrBD5Z*b1fjxZj29eNrbe?Yn#xfk%rbJ#MNSU}K3`FL+^7o6&Ksj5o znNL{Qj`HGPCrY^t?4yAM)ZfLVFf+TZEFnDy&5&(_r!7M?s7kud5RnO#{OebrJ`$@Z zc$_~RKDn4VUzUjA-s{yE%vEb3M)oXFOsx#=E_-S_M@( zWUvkdzFr$;Nb^mfteg)tnAz>Rkd;P#;cvkn5JIcOq^YF+2h(bHohT$5?HShTP|$=kq?CVtP^&RZ%FcmSgT4Y@u;emKhA-T z;>;;AITN-E8WnI-5FzyQ3FO%KSo+pU4_;F5)r!qV*V-b#*Kb=kvaYpJ0i7R~H(Y0Q z`JdY%#uL1^ozI-~FYm1(XuGjq4kw3jkH&a#EZsVAZ;0Qlx{yG6-P+KW{kg{9gIH3| z=n$>NGYm9X^t+4Hcv;>g1b6wKQ@^Il##)EH<#@Q{$N5WTJ~2G%zL;}T2BR8dx*23*u) zFu!8Hr_fn5`$j@wyc1S-X`}(n*MBUek1{V;vlAIyrEWdA^rZE00;w$^miBE3o)pRO zI9R+V5Zw<6*9Y_9{P=Ej@2Eqh^sekvu?*|CE(K3;;}S0LrQzQSV2_STuSYj2FlcIt zdRyM)m5_pT1bmTA{r8JF%pYSnHwI%qEiA)^C*8`pW|K8xpa9;7;Pmhpo;LXyGugZy z<@c<8OdqJpPz~@auXX517$pUSg1UVF``ve zSPlJ4(#SKbQ#p1acA_Xf33#2C_E8qCVUKZfX5Df%Val-(tqIG-eLA3JC3?!^7(8nu ztJLiF>dLP|`hqKG&f&MjDEwSv2ZXO@-m zi+HKd*bfyRk}_XW%T)euu&1JipmHy?#@#iep0C)t7@ zFXlyFI5z`XGH64(Xzw3itKR$ECE&W<_I~_%7H~hYW}qisAhOtXV}S)Gvb~vJ8w$=o z)v5){MU=0{bXtC5oYnKsvv`VudB)FR5y5u&>JP|(cu-45~C!i%&zY8HPEDjnN`0Oh0AT`_;0Q3-}9f>mA zl4Rr(kT>H<727{ynOKd_<7u+Q^)7`^kIw;0IHBPw7F-O5FMfQ9h{Q*Qz57Mi*bd=S zUbWs2EUHx2UjN?oP53;E^s{6xV1Dn6nDumIBXld_JM1m!P31&5+Mem&BP8qROF}7M zpm$^nr<=5s*3G=$rb+EYnCtrn@bWn9`yT`ErjG?>eI3BwHNAFXU6a{Hg{OkRml*QU zz?j%sN-#(E{~sa6mNauKE>78fr$w0Xieo-QhY*)DVW;XU+U+eb>$AdrJc`95D3|{Q zNvj3hBSrf@&jZeB06wnbV?zq!Z9M3_bq6R_RuT6)dL#4_Rsk8I;^%9((U4Jv;vZk6F%bC~>g_m`l#$UK^D} z7jj5!6u$ddh@7HQXUSmejARVi+6+M&^6J$7kfRaQ}Y=?2~^Iz zH!oSA(wC1kTQ2tk?QE5J)wDF)uU6IYRY&ya^D&88i8AMIDVGCQ%bVX%%>$5}@MTo) zN2GCkRsAjq4&Lt#b!kDQIQOP}_*^^cOU?PPz6->*sV>2gQAL-E>tPUjhxHEDeq-RE z=^TPPtaEco%M@(5KQ0%ktjS4H5mHM(pQbC-UKjl8>T>)Qqc#Gj$W{xb(8xV7HHz-5 zj3?9)RwdBCj}8U;eh8Br=W7&sP3GQ3{Y-gA1z>V1AJVmY#XOI3m1)-tp)wNTas<8P zkHz3LnU+S^D6a@s6Alt=ZzCBm@BjxxhDf53=F-cLsLl;`0fZAq!AcR>#o);F2UCG+ zD*_Gn?dldEKf`)!5~LmXev=nw5?&{IW)f$WrvcCdrxu)@+b+_CnE%9rY zfGJCD|DJo}N8_Xc08=UKexu=sL-7hH@__dTk?>g6=)Det3$&iZ7YT);QIKFv)Ye`x zAJ;I6xC{)=3V-yxuyaw6Br%e~MlrPOJVNl*0B=2hn@;4cTYy2O3hS8{y728sltE^E zWM0P9IbsY4mewbTK~I2V&$Gh}+WH^7tyh*+V&xI2RpaP(d2@t&g0A=!yc;OMi_g$! zGtzM?FMOieQxKlizvhJAfpO^1MfjY$cxx7J(I+MVhLq>Qpo1w#fwm*iGtfWr_Dh7< z@%_=#*>WXEh;$BbYxzHF3MTV>Hi+$}3BDsw$p#3mPr6TI*P8$X-$GziKiS zgGYZ#9QG^ZVvJ!08n42a?j)F1+pz1qjxs9xA=M$!bSPZ)m9EMaCG;k$+;{df+)<&U zxr2kuu8aDCzTs6hF2HhHj`?S|RwR&vrNA2=3w?XAG@lysUs%@t zBGU9M{oI-b;U9KyY3uA;l|91ZpkPe(D~%U~^#;uFvUvAEFID5nH{tc9>FmK+5rsZp z8opw>k+k)3OL50wOsfz*r#~wg<$s36XClj?>QLyRnhecUZ4^OOrz|RP96zoH@D(kV zO>cTEC4p90vP96nsOQXKr3Jx6<)80Y!inE>0NDd^w$qBX<*Cth#GI^8{M#QB5Mv z9YDTSUlD<%FQ4(i%1B0Q^FieA-N6u0FI&_CFhM!RMbF%C$07Q!dwDX7Y{VA%x>KIQ zdEYZwrNL*iJlwO3?7UlZh1ankwMMb@np22M=Fu?fU_*l)wWSO}X<&cYNBs^?tSt_? ziPnzQIbGuf;)pS^ov$Ut5$iS64^lUE9mGlNp~$7BunJzg*qCzsG8Klo{j4HHUEUq7 zl5!SQGB33YkI@8}Zs$Z){!W4KK$2FpgOFDznFX29qKBr?L_K^l9FE57;Z_c^D3gn- zG*_o~#zeZUgwoq*=S{Za!rjui^bRpzF%qr+s41buG+}%LB@Wf|2Ydy*qK2k$!_4_X zKvvVpyRSEAT)>`${-@SEwS30VRJAj7$bD`8FU?6ha7=M{w3{kH*!RM;04CiF_a$!x zr2yhO1lNQEbh=@(9?f0^X3kFv3a<=6+%;g+1-aM_G=VB!`E@DHS!xH9#+MoYLgjs` za|$>0u_fvB(<8$%4#|)^SMn)QT@+CF>%K`=lw(=EYFnTW)Q6a0^7XWXBIjGL_BpIU zI>W)o7-p#hoh!3Cj|TJ5p7#!O(jtw%2X^RCz56Dz;og2iDXn)T$`U`z9cmD1NEvQ| z!(EGH4sur?@?&fl{P*f>&E3jk65n3B#&n<2oZIugC_1UXGk%HjmU-zPHD%^1Ju4~t z@;#*i`RM^+&eH)QdYDD(?U|mKbYw!#J%6CRH&+EFc?cGz3_;_?1+y+GD3Nv@pAyd- z-cwY!4r8hkz~qi>f~{Qo-MgqKyYc-@$Hn3>Tkwk)uVXRN*w-xJowmVh67uTkRjIq1 zU0jAO%q~k14lW&BYw5Mq<)WXf?1%J!c#!Znn`k88CKN-$+Trk$^-r}^m^QuFrQQ}u zhAnYjl)n68@CI0fcU7Tbelf5*t|?sGxH3{r)VX|pI<;CQ7LbAUOMD#>fRvr3gqTT$ zE(C!o-fFTp6xT;{Ji3UCG2b^~x)w8*y$^;YJ?0D|w>Kbiqs=Y~Xef@f{AyABZEa`4 zsF66!ireksai`yqbhBjhN3@smd-J-#AZ=#9&)DSDB0V?`YGwF}1ad$437np@TT1puut{uq2iLK{#&KrgDJXHfAIcy|!q$aMME~03YZ^E@#<`|>JCw#t^ zb&+>9q1x2d14E+m*Zpd9p4y4Qm3@aY5BEuU)A|i-riLTNx)8!kQLnClEI0O*Pa>`7 zm6iWxuG*IMNIn#_TEu>FBLtpMCnY>%3y_O6aC`})Sg#{(dAZ4Iy;Q-Z zu9B@!Jk~Y=Ab987X!x3vDx9k~Pq(<$?#gG9>fjL0$sGJc@+^0x6BEIh1PrX?x)4Z@ z@u3k6^$mbI?DE^Vm9h@{d6MBm1E(}l6nuIrcse2Cp;o} zB9<-GWRS;lESF`E`kqYirT*GjHB{-5KN!+rZ-Lb}ZUjUga_0sktFPwN2i3KP2m{7c zWR+!{lmwZNJ_ie`jx<4^n0ODH>_erVm%5gq>T1ESH#)vY;I{Jon<*5zqhuLGSY#$A z-%u7mq^#9l(B|83$q5JP>ARJqT!}{503^(x`vX}fKOHtve{=uW%!L~M3ER4ckY%kO*eg|d{5rn|6z;6Ikzmf3Q2kgeU# z=k|P6cw6{VLAKG!L#0B#R)o@zH%{^fPAAyxD$`@cKil*s_u4_Q;_Bh53-!3I7ZV&~bKa?v*AcP7tp z5jD@(pvc{!NRrpW!~^sqHiJ8_lL$M?`+~obVqx_K=S;OW{{%KrP-uDPYh{EQ`2-&Q6tM9MXKu$kjbH5NgB(@vjP zwK3SxG{KlMFK=rldnJTqN2|Cp$3c2RJ_cg9d8}=P$bvbz=k5=-`^|9cu%CqyMY4)X zT%~;0-ByUZbD`d%i988ZFNP8{5NVX4$Pt@BKIKk>3g!5>fADU&xLh>^Lrs zI9ukHCRr9fugAoTr+3WmNc6*L50AwBhUAr2cYnY1$U`?eb+&V<2?D z?BWcW98hSFHtfJ(G7^7=u@w@2u+QMm9Gm*-YCFNO+*oQmDzF%QA0bWQ2YaSovg!aI znGLeqD|-ey;$q^RF-i#wmfH+}+afR|&O$UzFJa2fP@KTK$I#76+PyhrYur0YsT(ieA1y(=C@{;Xf%EWy zWuWa z6m77Czm<+O(288iISAXV-H_CDv-hjq@otxljdnWBV&FY>W{DOQxpqqF?%N?MI8jo+ z1CqM-D4LIgLMdK2Z_z98!X(Q@mH-}98&06DUcNpvCzHTdjZDf-5qxL*3_-b0-gR+J z({)enK|m4#@Lzw!UnrdRi#ZMrst~^+(82Q}R@Kc^1G01UV4rz7Dnc17So&3_?}&mc zxGj~|sx)cJ5fcjiCpQNJkFC>dl|Vdf(9S4d*VtE2k10e^T;*b;H(djv*DL<*Q9Qb( zQKs;k1grPoNV+wPsoo0`;ANV0r(ZtJFllAY9R@B4B7OQfW%YV&?rd|tHRaxi;iPP9 zq;#47NLtL-#{B3KSc^j)vhUJ*n<$u9;D6)iYu%_XFlUyj|ImaLd_uRA#h<&W-ibcsSEXo6^}QB=n>ze$rOp5cP*sS#B^i;uOCaRo`W>5ZuU} zZ0V1&;-36C+zslBKkF*REuk7F8-VhEPoQJC7b(khy%y z3SdkKCCpq832d-q)o64baCaF~)p+|<%$*NPdB;5ia`Ej;+yJoO+!ZL$r<@XZQD@Ml z7pLza4@t`KK7!xw%@nR9xLkuXr=FR8z#2)d?T8x!%J(h3XFe{~gu z&Uh&HZ%dIrCseP9@I`345YUoOr~kN2o!#{J>L=VD1b-ce1zQA9w;?kI#BOMac4!!-ppwx{8aS&tvc$^Bk z?uEtC?S5_!oNr3GCfAz?LQwWREi33=&aGv4ioof4jFhh;QU{R)_i8p{8RPJ|cgUbz z=eh?iRn(b|WlL$#sBfUi2?z{d=oQdYH_k=i|7zrm?%uA2m;M5WkP}{jBwB9;lwm** zY4Gc(hk2O>!fLcOP!S4Z`CJ!0Hym9X}!u?|kQ7f#2xF?|rKJ|bU3!$8RYon8KNh$xc8^ddZ- z(2RI{sZ!qz!OYhtLHd1Tqm*CqMGFpCz&`;cZ5Ty?I3-KU?=eoJTt9rjnka`TK6Hqfy?#8rj;kG`bYV9)~m z|4peMBys$}TeW9|^=6;05_4$OH|CEpIg)hupL1^wnGpl;1c5pFN5UR|6U%NYsdN5 z3mIKIm`xMJdlY^ey5!`0l-hMh*ZxCN@`~%1l1zB1%7B4M>tmG-)nTmLP0t@2G;Ed7U@k?gEw+F3wC$uUaKnD&;iaM_ad z1g&NY2I$MRY>O5Fr-}>@%>Y>+fTfG~oyHP{d-O4$JYG>=o!XkFJ6^94m`dx}cN&?T z;~(-^P=_dw$FFx3eQ=a$S(i@Qyjwu6nB0lH;%!Kjo z!F58qqv_$FLIj_%V;MsGBvrq>zEw3D=;s=Tdqc-fQ})3@uWR*%D*qkgyZ_OK>n`Vi zZHW47>b+Y&g1aCyg>vh2%^&D4ef$4NxSP)qYWH1A?d5cf?INeASM_LQ9v1n>79_H6 zAF-Re{eL8!n!4KQiyB~)aE8%I&A!<~!llaI55|DCjcF_}p!x9;sB6@0CTcVj!i2dU zg=i5&hl;oK9I`S{ZQNUm`-sH;;~5~K`{=LrlaRaMV%*-iw!BbLJ2~R!B8@0U3hDH* z85QH6|5d^_7GPu#Hsk9ewrTRHewQf|vDm%18eDz^>*VH3U8!I8{vuQd=Ge;w z)y&fVz)qhzT*6QYK`UeJLBiUV$NI6v=b%kiK_1ML2K~sFf!NKC<{0{6AT{A@mj5KI zDhYlcSlq~;%&oaWwnyXO63Q$R2Kw>euA%Y<=aU%H+I)hZg6E=FwZpG(!nSBs*CJtL z)W;5xIuAv~e*o-52}`HX>)u_7z#*OmM84<(Y!Fpn=cz<)=^NeUvuUi<$>Z* z{#QsL%1GCT95>Yb81G*T2^(6(tP~OJAEZ^C2e$Vb&HcJO|G$c*2f5jPK?w$#p4cS( z-K*a8eYT%_Yiia;+dZQM`a4MenGU7k4PPQ*L-kI0(Iz|JuC%t&S#e+C9y+`oz|ug@ z9ti~I`&g1cb+eRO-K>?0^FqfHw96A39on2<6IC-5RfRdVv7Bjc7X?D zTLm9i1SrX_J7y~ykeOfI(+=16Wrwj{fvSaJ)Q9Zs1C4Ra&nzzt7PWQLisf1Dx+iet zI)TauYyQVEi0wIJPESatzI-8Y&*@w%Qk;j{?`pR+V{XLE3 zbDe@PkgMTiod;1LtOLZs+&P`4=@7DT%3)haC@{uMz~c&*Vq%~WUrZT(l03U^N@dNd z{TaFp&@C+J5eL;bDzrt1X8)*IhlpYW@rELgB%v_S@jsO@9G%$u-LodA8EGIp}duqzQca6 z$Vi)N(BZpO)vGYjHRw@V6{=|FSbjTy5O<7eFHQ-6^vTv@7)Xob(LFW4O}~t&D}c~B zsyruw(WY7O?eh%-(Rn?Np{2P>ye02SkbU_faZ+6O3xujVP(L`o4f!_+Yij~e&Twk9 zo(1otzwdO1vHg~c90n4YeEt`yw$F(Xs|Cudp6J!hoG*G*bwM0iVW4TJ_`f%O@@w!j zF7x7ny z5cI@PukIxp`QI9%E`*3B_t6gVgD44_X{{nSG;@4+rU?pgg1{UIsi{w1e*1vyViZ?R&5W*CRy zNc-DgB2nc`$B9+>_oSLX1m$T~><6!90EKO*;BDmZFW&CAb&m^f6Ifz?@u$y36(+c2 zI0N+Y>2Y$857(B{FZ!UZzl6AkHET2awi#P93?%jPkspOgjOrgbc|sJW(VjYO=tXC< zeE@j+Df`#ozm!^=UIxTv2smXfu(y$Y6IIy!fo4-5$7rwWf{>!~p0|~jc1Jg@ zSeC~-ybJ@~RMPy#(S8x4k6;?HmR|i<&aY%h2>G=78}OGu0Up+L+Rsld6}Prkh9IFV zgf(P_xfg+$kl!ElUI7-m>mj-HXS;)Cn3mx7)X+(T|0J=VE7(<`g_|yzK@A^W@ ze)=a<#XnpS*@I=kU)&$Aekoo+CD7&TW?ig-~=cu(tnOaKDp1vNf@uhZl{g zP$UnFAS)gOnxDxArY7Rqna(+6K#^l5K=U!-ArWXX>|kzE_$2KX&~V9*@lKehJl%Z$ z<`Wx$={m_tSB9nL`Hw8dst*L+-Nr?Ie*AaoAL=A^o6!$Gi-Bi}oav9%{$_T|6^BOS(IxOH#VKyY6!i@2h-k-F3h3 z{>`&E^PAc;vuDr1eT&JVI}`Ux=<$J1YyV9Ne*oQD^dR_%aqU}f{UyuC*R3}QTg%~U zr@ytE0*CHptoV8_W6@RyXln9L60NKq(rw8ME(dH#}_ z65+bBr@F!sMYim#Xz&=Aru*cX4HYpj6+wz#)nL4A=#ijYNHy=XeQuMDj_L?R95pSV z;-z2EQXg8TMOJa;+=w#eQ93{;T%e9YD15mhU_PVHb^?&Z);Y7+XT91jV&{;zLO?NQ z)WOTYNF{WK&R=D1>BnepZk+}CBRiPyNV%##}^%NA#kcc_Ej!Apt!C0~NYk=Kp@Md+7fPn%?p zd0}jVPj*BQD;guu$B9l;`6EvT6yIDyHdZ>fmr`Hco6IF-(2{Bk+Z{N`e46uf|bJG0$unv+U~ z+c9hvwlt-V+*>40U26}fX67+OW@K&rP=F+U;&+u<7Dq@Tr~XX+bGiHZ9P_oyS^H$L zWlru~WGj%)2#{CXeJe6J5xOS$LX@9dUqv|}Ti3LU12+kEEDMBa%^`n$)Tb;mzVSvR zZU*PER5yJsz0}DBv@YvZHDS8_6ou-VFL+1V$d}SLSF4OaD@Sf`!>ihLj3D#nb@lpE9d~+ zMeXpVtz0CTG6O_}uDF^0x zB-Y0pXu}v(o6JWfGO<)k;l}mkqEZ)MkRzk53#bm(z$E?S5H>mVI0?r?CAStSe_mne zs+LDF=S+Ret4>Z)=^0krlmaI6xU%LoqE$V2^&V>Tyk>Uk(d5py)RZW^-Yj)x&+=ac zX!>7Xm4g0z22Kz98+@=(y~__XkCXW*lyt{3p72EOM=kbWrbEA`>BF||S2MGP(P<0k zOMhl7DdsE^&@F44x!ja5!^@t(u@3(I!~LYLyES`GC$88NMsknW(v%77!oT;-U5jP2-Vpa8tiOn@t|Ai-{eTaXzxA&$bT#2 z%>(8=Snu8(jA-O3UQu@hdXbQkjkv~SN;>%E`(4+8KY)DEHg-C}Ynz?1%KE+JM|k!U z>zokX0~Y+Zt~&6)`A5h@$9&D1%2bbnyf?S0ofCqN70e3cEOleLjN1?4vlpyD6k0h% zo6+gsJuk+ce;v!3-vqDT`9;^B22W?DV67Dwg3~+a8@zH#DmhiC4X!10%t)fFP4DEb zS7{6NAvZs{gCe0IJh=Q^2MM~s2;25ZUKEj*>3cOh1ZEem(OnUnlK^)noUoAe0sMvk z+K1Og2pcaBf6}${QAa1^lDh*_&IKM^EFrW#kLqXqpnSBCn*TloNqDR*ME3y?zWaKi z7(e&lw|XSumy0U##DsPGa#&&lv96OTfVV#THJAtYwue%;5^=7D$=tk9+zD0t^_T3v zM)n&O!#8qkpFhikOg;eV(Roc#9}EafT(habVVo>++QHj6FUbPWrirUr2>EIoU;*l~ zo)v?`dqvj1e$LG$QSUqTo+W4#cW*5L9s2@sOp9Q~jkS~xfRN!k0mlO{_ zRC`h!S)$*B?HhX>0*U3o48R;FX(HBye-*X(lLs$7fP-YK3J9^6{|SFU$5M_rMj9sd zF$lTjjU6R0an_ZoUeQT(Xedd^Vjrxnz&~>j&uJpnjU!IH&i*j5!=ykwd8RFL1mDGXP7?BQ02|lx>CLeRE)==#wmvDl~)>l&iEoTr=!4y1z zk>TPv`&F4y3hEEfY-vY|J{jRiE#|%YN&=Sf2}}X8_=K+@*A0K!+RAM;0(50b0vx?_ z;Lq&mDxG=75003DL=8iop%Y-(*<)#*B>YyTqN%&fMM)8xR>#{$^i3aN{E!dbdI@Ip zT-t$fcjBim`C$yzI{0Uk$=+LN;zliG*8t1Kon(Razlj3EVGG@avKR3vp2GHFpEK(A z+}{jvy>Eq#hX5%t|DCG-abSAXrC4Yr!CBZ_sc3sxxBm+v&}ko}31$|=u1fNf_*2x0 zDm^o%fXN$!j!n@d3x0q4=%JCVZe?VaG;+E=mw4DS`Ii>r^*KbEv7QW7O#kRe+z$l* zn}6&op0Vdd(1Ga7%6Wmb+zHeC#%ZJY(_K{fiC#fW#N@1PpfHqPMIrhn*K(l6S(AQ{ zmQ5%~SZEdNfROhPs_n9VNfJQ#KMlKlKXmUJ0i~jL{^(Y7PFyrQGOPac0@)C$N1?{o z5QR|?oDYGO>i<3r^G|8-B&q*gVl--dBSpNI)_70*S3-LJz>PvZVPIgB2z!nq_-OnG z-)+z%!@FpyqcqhwAZp_w@(Q}jO$m{?dZd6jX+HR^GGHw!Sw|EyNS4l~!}$HuyAVFQ zUq*GSTXNs#lbjoKXry=md>ic?{PC9F?xwOHPESS^uzcY-23lfVIV4Prm@#8%^}t#M z(i#3MLE#ZjG`Y+ZYjqii>1Zev40rryKt2b7E?~}s&590wKlP2hsKhXoVoos2k559z z@wUEy@b$FWZ%e`|5AXW6+1H*nA!t2^Z|XyTbG_b!)@ozD*z-U`518`u%9>F5OPH?G zgYNM16&tNv=J$6csXJ>1P)}@t8#^|UDq2iLxGv>|T6bESWnZx?Zc}F-?&v4P0j3Kgzb^^_>zx5R&lyF{;s(soP3F%xXCL^yUDjc(BYG6A zZ$G?ZLYX^w^;a8SvG0YDjpEh}TB2LHKfvcnMl_9dvH0sCd8j@g^NwA3CZ|Pp!xK(R z_dHo^$OCA1K}&6kBt7d=%od(bl}>+wzL|GoJ_6P1z`S6M)@o78@xSV)tTf5L_;z?( zUnN*0liJ$6BK)vhxJ>?fxyo0oP;V6+=l&1+IX3gHhsf6PX~OX&t^Sn8m~EHB5JB+P z>F$@WkB2SMgTVOiogSSx)ad!Rr63)A#vS${%tAQ9z|RBsMSK(7UVSaOM{htBn7wl- zufG*_;k;*~M=K1E1!Z*k3e#c^>5=6#DNAM2|F7_rG8rN8AcvMr_UPBsi>93dx}PVp zc_}F{`atSjT5CoDrtE(Jn2-R?;2mMx8pgIjy&fiY;iHLpt{d5psx0IlIKUEtj_?QI zi$L;T+f8+vx!SpM1Lrvy~jxP{!{Fizer&74^gTVqQEsJk9qbkaUj$GR+30 z+}ryHAe%v5tua@qTD>Z#uhDWbov5ZBsb9Vi>E9b=SSERXJ@2FCZL-H|Zz&Ml0_Xd$ z9%yLXeEXf9j4mIS4``7ujQm~3=r-IM(+$x<6Ar0vQYWN5dM`m?3=%nkS>7-4GHeF+ z>Y{40VeFPxwq?vo&;|RKJ08HPjA|k+9;rEYRp31GOV}IDX!z_bBn{T!>M3UTQn?D6 zI{*-`RS^wV+{fIz7AD4KA0b33)vYx2N^K)(03`zkIm0`+H3A7s!p_$sTtHxG`8N`c@hr}z3> zO;ArH_zUCv$f062;(B67;hqhV9X6Xm-XGorRJPqfXzAJ!XM;}2tHC~8zMONkA~$d)47I-`mcG< z<8JxiwC;Z{4qm{aw0KO$veRgqoiBh_2LX8JTLUR(fyp;Zzu-0YXl&fSh!tj&(TTIlax48*3v3+ zf2TVn+v?k{6a?UZ&mZtGeZYi4Y*5LM;}dqe>{}+Wu2O8JQo+eA6F^~!6Vu=uwv z>C>B6*122BZjT8Am6tok6CqvgAoGvhe^fM{m9?ai3EOIrlx!>Va7AqjrvX^a(U80F z#|@n!s=S}q`Pj*(S1D(--sYEkUI`@Te~x+Eo(Okk&=d^y3vmPOsfffE!0S#WJC~n9 zzkWAe`hr~ls;I8-=XSlk;sBTT?%6x4iuSl#1~hiH3$JHprukn;<4ysCUM=71@>9(> zy5;aUJw;$7^|Og99x$$d!c^7yF5gF@6aYAQiy+gTlm5;hCG5RKxtH>+`kYr}%4&95 z*J;Wl($)JX&P@Z5eG(7_yj18{wShgIIS%B})E@fameh#Q;g>Ik6!8b3l*^)r?MK~w z_f+&q5rz1$oaF{!bn+j7)s(_4K5tMtOL%Xu8c4>kbwwj3pcja}h5Zb}OwB%(ga6OH z1aa?N2=uZzjy;FJd*s3<weoS>jQ&u1JFyc8+bL}_#l zeY0Ef(oEyZyr{?JqakB0{3$c||NIk(_W47h7i-4mu<}zeS592`+3RRbzr-AI;T%U0 zubC`C0LCMHf_ne`^9~^rA@-qy(jGnI%xt^c#D`A==7CxrS8}%iLKhqyHPOBUSYhWX z_M|>GFZuJRlv8Wz_Q!^a-K%e|#K2z0LxY!El_dCF0wrkfdCjQ!qmQ#=OFgg%J}8ol zIm-hsf3=G9yVpU?`n5wTPS{lD#wrQ7`1L7HX9_QVwp{r$W~WjRg-b;nl78>w;Vugk zArFhxTAn;}Letuu`RuMc@}zJsIzX#P`ZB1pn&RqRf|SRzl0?Nj#(qz_P@tW@mA$&@ zYA}Js#$SIcn!kY^Bky;)2`BnF9XMhcsbtQuNd0wXe7$W~GygA1{3Qngo~G*EhC#7B zoyqJyc1-U>gZK*chegxtK4vwx_2(z`zhi!-&LXLPJAZj@_PH8W z(FqmcQh6-Yb+H0+7tMh)yq;;v-P<6MmAmF^)-GpTt(pys9Wa+LIBReH29swmVdpCe zJGBHAX@qEEjG2YyEHZEz8nJaANbV+*QLf>G`g+5q`C7{H#qGB+wnnxUv@N?acv3&; z2vXZ5#QVhSx+D#)cSjN;6N&K9vrEY!m0|^xlV3Wdkv3f5X!b2Ff9P?T8 zbT7KVA9AN?j~tIYEc%8sCYZrAMRfZ-n}qsk=*GB-6_iF2hrpt*Qiq<5#@=05^O>hC z=mAY%wzWd2T40b_U#})^68-oklhUS6U7BXuvd8=Cf-fJa!_j8ZW->K2ljV{K-3W)` zfZVzlurqvFE9Ezp&mv>V0fzuH*MIi>IJiDSAJ4DefuuuQKjll&%Mgk?YbTD00YB$F zFip5x2VUM!%#Kc#8w1W0POy;A>Z_{opM3tRl#`A4$?zALrh5}MspWllVY2(>1#^&z z^?T+zV;8dWdV_O7{I)}CXTl}H&tRE5 zqvJ;8lq839vLsq@0UL`+e1+>TNs=G3X4q(G^MwES278WWGG8QxGX z`We-roWPg~M6imxg}I*CI49neaUGuT)K*zub$GA~l(R~T@!KJZodbmz7$n;`z4~<~ zg00>FBWQEZ$j^pODUNcVi0m_+?*{uFAw60>YD!=8`7xacqnS|!wxiqCK4DhK$%Ukd z>J6u48$1&Nlk|_btgxgPe6>m15SHTAP>yw5aV*+(s5LOmp3#*MNt+`T1xyiV$W?Y; zXhI(yl_Q_Xdnu%CG?XyFre7Ciaoy3pti%M+CF{vjw%&vVrv(f`bHZMWv>>I z#jx0;s{ss)Be3Mcvso(AB@4TApz>AKSBtitUK-=^1diCu1iTUi6~BTc4EOqt)MW|1 zG%JhcLqK(_mBgOXJjH9JqrpZFr&;niB9O5LL?K4qVr71aVT)(hBLAE#=jL6_Y1XU0 z?q`Wky9e}iQgwhE7~_DsEZikg5W0;_o*OyVk-$;)`_NW8<~Of{|PjAbH^!1D3I z8GMF1fRTCWTuZcO_j}~WdG0saGpScDygN6Kt}a_O`(hq2JIswu~C0SzXKq?)r0rH;^nZ&qedz7@kZp}$xYv%XM7z+80?Gc*Be zC7{-&zN*HuuP$hDk)Nvy=!4E{8^oEO3qM-K{mjGpY8+By1*M=-Lmo(h5hk*;;&-K? z!hv|gJi|E_!sM}XfH_;NQs`i#fy5ynylRsXRhzbI`xMLQ;aqIwZm57e*gsK*-z5RX zZb`U)L@Hx_D$TOLH#D4b-l14(Yv}vwbuWsmuu-exqX5 z$x!+F6C({lra-lXHiXWPj>-?OpRpS1lXLFZD>UC#qPz3v*KMl}%)t^`0Kz4MOdfm1 ziAE0gt=J!!Vi9Kwx>ux;xwFwqc~=J%4c-tL{`w6A{#^+D={eRPgE+XMcWqeEbOh7v z*-iu>0V^mR{x;dX4D`2~f=2q#{btXtIQU&1hh1);Uq05qp}v5z-bL}yV>d^~G#r*E zRNOLRT#w*3c=iqw7k&nxTai3KSW@BsE4}7JwV%`10T;>Cy|-xR!dbIEpR5B%9)~hl zPy_WY2NchPK~u5TA5OX6gC)+wl)9fR9TLLy$2M&0O|8J26Ku~EX<(v<0uOqUcKKIvYltP zFwzOkUmytSp&I{WGh>|aD_?Z2kDOpHm5+*d6pc5%2q|V^_$>K{zK^bASKGwt|a{HO%!lx z=^um>RDyNy4&zm12>Dv5N-lV%t0|tNp9bCtdZ-Kt*l*kb6LH3Dr;(#L1?X9vN{`h2 zv}ohMM~B70Tsp}Qefm^U4IYz9)n|QpJ9GJDOz?xaUx<%Cj9G@`PrTPPnVa)=(lO}w z>5R>jzjqyQwz0Y`3@$KKyJU2Zoq5eK`Eh~14yYF5KMBvk1j;;GAa9AwixbUXoDnze zI}w-<>=2vS&i~-K`s=2sV(JB9U%mkywYnVg7K(wie0lM{^p*!&x~@Mp85R|v@sxr9 z|7KB2%n%8uXkEXJsj1577{oFFJUikWvet2}@btx7IGa>LgvAaoDBL`*)ud}`paH?} zmjN@5K|4Ap{U30kjkmQ@66~FJCs`Zw*XJd02Y=4`{`eg!2N@#;^2W%-{?`ttA5|DR ze4n{;9Jp)ZZsB+QhVLW^)YY-*|5z%qe%mx_cy12V+nVXk&v3KB(?Nh8KL6JaSG!SK zwcHY)hp5({^|B^)8*@RsF)^*aOVVG-{N4^O$tjy{kfy~PKk$3&Z4+AI3u=E4cj z7^nE28IhBPFbSiSjHHq)Jdoi44JqwK_57n>nRAM>#q8lo75s~A!vsqNveOlNAx^;D zM$YKxQi-W`wCQM~az5|#U?Y4+$MFbvt~)rJ6ZlMf69I(HZ7wQFJVds+H?M`p4+Abp z7!oU_%i=|?dCWfQAW>pL5b8Xl`hzgmEJp0y&W$B5)j3D8S1>z0zfKQbF^m^-|8cH%H)*ll%f*5G8+lU= z^8vzq!sZtTF9_BQEHimGr;wk%OPE9~CiTmb?0aHlN`D2^O&rVinnS&)jJurGhN!2v zt;?+MvgHszy$#RR-jVvY^91mvlpZLH)-O%h$Zngg)nqPK36N6g^&}9=cdtXoxi(Gu z?`7x(*;fjY;Y+_U%?;m{oaG-S%bj9aYz{vy&HVD$VX6P{V1Cgx$cmwF$b%hc#U`E` zm4vu)=Yb5RCgsqM3TArAi{XEkdb@ZE;-);FC0{@^aYVwp$Qs6efN41ANtQUdBdha zGB9RL+JS40&j>YHxkdf*Z_VZjz@LD9=>=Bu37sOFrY@FrWRC&p3&W(U+yjI(M!(ce z+0yHtI$Ckqe9AjK zjDYfG-N$9mbBY;)?P1J|gk8`jjQ5LiP=u!A3A^ze9h)worIN^a84Hx_0({W8iiUJm z372lAwsdnZyh&`Iv6W@MSc4#hLV5xIT?j(P*rR839Tt5w0yRP)gudBr@cv7J6=5bJ!v`|tuy$2e3zw~ts**(SsZ=@tx;x`99qzBwI=%I9M;cj?DC9W} z4MF&f+`ka>03qqtG)VXC;$m`G@b)@l@w$NX?G9W3;=oPjMze`RX~P4AH)==kB>3JF zTw}f^K;;thoI&^aN{i<>h~+QfX^e)=4^(2@1#)Oujbvn=$9wBE*uB@as)Civh@0-4 zvoouB%ft^LG#q-Vyj5~GD)4!Yy|m6{3^p=!n4B=hp>XTlqeA=2S_neVBuL5*ks*`Y z56yub$2XDkn49NjX!;8)mq2TG?zIf09TY6X7|P(9P{FpE^aigbS)-CwzwPL;2MFK4 zKYfcYexXO3wG|xis)Q~$M<}ult?MFdyX^;^)PC>)VO11xEBRH^e~*MJ^9BJr_OSX^ z)+Or~(od$PYV;m$E?_O@=2&@sP4TE*^0U;EEKn(@uP5IF2%E~d zt;sb^7uxA(nuAW@^7$P$2Xz}Ng>@rYwP+*|OdtqNwEx@iRzAMJ^Y=U!?0+Hf%b=cc zG`$-L{>2?v0L}ClA%f_PDZ4sGZb9OZfU?Gxq2Z9=yU=w3#cdoFq1oI-@exEoUGCFE z<>auNE(cYVhF2qc(aeMIE?plfo~o}s-^9PLKDOcqszi7nzH1EZ4gPi$a9^7hxv`+X z28x$>a4}!^x`|S76>R{3YMH6;9Oo9gROYT*J}Y}h9lya84)9~$M$U0xiN14D1G2-r z`X4EG5xW9Grn%-Mw^j3;1aFB5`cNawqDuT*C-!UNz{0yHiCp!q8E)KZU^gqPgn?%I zM3wN~$@fDa-Tq|3q3U>KU}D!()367b~T>*xQobNc`GC zX`stP+(CCr$dYo%=H;T@LF=HEAid=zzgG*7@{jU1cSR;Mz*`C6p67(~%g*M{bdJ*qH@LsEyE(Nhv@vBL*NT!hpsJlS zPJ<=)4^62h*4~bEB27nm+1~uFg+&*?UbP}fVGK~$|5U)K=u-w36YdfWKmT6dc61ATUK z{kd(tn};i(3|-JLvz3EY+F3SXE{Egcga%355y9G|qiOk*$1WthQ^8i;L145N=PSO^ z%zJyH(4oe!>*r4(Y_g4KF!%fArs16`B*+qUViGB?Lr{5GulS_!AeF;;1QDD7-IJs~ z_Ug`=yDfu$eX4n&H&9ad6<#hg?{1;)@K}pd)}vw&1;zkzpS1sP#ERaPn4L$_AKhHX zMs?)gVr4YUaD7Ss29N}Ew3x(RW??(S9>Vmg?BmhXoN42|0u5+cTAa=JRlW2;FqeWg zS=WTWblSvs*CKJ*bC7E~ur~%y#fxo4}b7{*CE7A&`QUCvE#mcWsNs173e7#y z5FGP@%Abc^|M^9h*f14>3cqlv(0k*U3io1#A2z_d1GM{nu#jId#=+X3O&warUqGip z_G<@*#5Mc)<*Nr7g2{k! z24N$&OT!NS7;}Dtoj4OYPs&}O&mg++K;n0E*;v^)*vg-ns~Sh| zonSjrM9>eS;3f@sMv`~%6G5+=s~{(?(Au<1%(=`b*gn)lF*SXK0Z5#G?A@lFA@Pc& z1qo+HqwK@k{6tf+d#Gum&|#KhhZ zQI5i^#fWr2$7?ej+G1u0BfK}Wr+Y0$Z;gGb7g(cKg<`LBO`i9KWneQt)KuXV0TUPhx{;b+f2dCPMBmZov~7yNg?^6vzS-*Ruh&eYtFw@NA2 zzPZ@33H<0OhY_-}GFAo!b04tki>Uq~-;^6Y(VMK|(3yD+1FN=*Lw?`%1)$QbJb#wJ z+<7@{4js?ibd>XZIK;TV#3o^hegIGhfi?UB#_5GB3x}0_%I4m8lgk5BDr}_2#6U$W zjJoo_e#Ee9W#TY)4;hY$HzhXJ=QNl%q|%CHo*{m?V{aIw3JfFE}SpAoPrM+W( zu2YBvRk0`N{*_NZa_<}yK9?!yR;}ymA*=2(DIc+4{(on|pDN>PME2Z-_z#>%&iS0w za{Ff}=nHSHBLf_(nJFJ_^=SHnf9PI1KEd3=qc2S>%rdKUYyWTQkRqg%zo|fBX5=nxldd=)+`a zcK!3k_lRLwC1e#;%Z!01c99zx>B|q<_BfX~IG}({y93)DqVan=~(aoN6Tg zK*nQYnQ7|Xul940Rs05vt5f-fLnBUH;PdTfH56O04Rim>m~;1Cwz^WI{K&v|SHTE= z9+%spjf9#b{a5yk#u^Dqz%OIUC^98=UlW)hN^8YEl331CqN!*5gi?+1c3pgOOKc0U z(#hYZrEJtnm8Q(3pH$iGFUfwcPIFs@I0f)$H%&>#hC*1d>g)KYR*q5XL?in_fZ*Le z%!O4{f}k-@8z5sZ)T}=M+haeXIjr?Ir`8k3eZ6HU7V{GXIu3Y~)Bb$%e>a4GwE&bv z9b`5CNwnVX`4~+7fV!y1E+g$bHnnuicFHDeR%5fjcC1*B-L37DxJuu}kC@|8NYaP* zop?hZlh*QcEa9=3K0L6->-vrF^7}m(3nOQg#&Bth1y;4zOsev_qkg`@=HZqDKnnuY z&L=wGhwfc$~=;Ly%v-Miith2l1Vm z28E)M)}w{4V>nyL7B3#rxZ{2K+Xn)x`$?!Xdt7Y2M^R%wAdyHqEi+QLOgko&zE^~3 z_=3t3{7W~$wlWD8Q4l3m6Q`CAtVnbL(~dP&Wc-!~N{kpeBGPl;`a{KnE-z_0AabhG zp|OO<_3MUJ`MLTmru@|eW`R|1<`?To*V_q0JXUCN77<`btCbxOZ!C2#Z5&HcN3w{# zY@Oqi5rz*@c$eBgKsYW0Kx6FZU!ye4x4(zaB|GsB7;;A#k!QZp`1JLdc(uJk!hQoS zW2yl3#)b;lqOM$Kgpl?_@&6D!=zok?g;BI24boAAg;usHrUbR!4%mV=(2^;fPwSte zX})+ZQXkYr=@aK=MmOZ*rGQQeLo8%nmx*jq8x# ztbTv*F&Ale85;gL*Z8UOQ47sm;D(K;^~Q$BCVj`~F|@@eUKjfB13!i4>rI9Hn08Q2 zyLx2q`DDUPM6;^!rK0=C_rY}L*+U1|O^xNicc+3cNt1Z%=zBpFO0$l-Gp+9NkJUO* zmkkrr5Rc5Exf(}3Ii%-SJIO`I?)k)XKu-ykbJtCQE@vzC*24pK%*5KoTCDE_={L9s4rurq0C*5SoZLe>4xK&hMCj}Xg~u9>$2Rg6da3)4pj2v z(@c9YR*bIoZ$>oFu%XYKb3C zcF0PLzxq_Up{XTcM#AURb$NGI?CBB56|b$1qgHQ*qNtv0aFqkBF?Eo4u7=9!uCca1 z5TVTEGb_D)f0nnwZp-#TBxfb|$vvMC-<~EaC9BN_G8Ug})|z1+$oVS>Bh%Nk^YzO$ z+xr6VvB)T2;O*Mk>Kaot_r)mtSmqfoUgRavkp!=%B)(iAr-bkcbYL*u8uKermLRbx zK7+SKv79sQmMzi$k=^4KN!|siEFe1)60LnB^|+wi=$)XGGtry+loim&7AUvhkG4jM zA{TRji+^~SWbgeKKEy-qq+wI8L$8Fy2-V6lQ10CIxIgSTGNU$Wi4T^yA{b=Be|rZz)5B37wiM z^irBWC>bCM2U}GH?k2i?sj8$G->1C^mzJ5&*l5Xmnik+cw$5aDK;Iki$kgIF+}7xY zoI2v}J9ji|6G5~>Dl0EKv@mIRHaaE&5Cvj>-75>q>-@uomujaSw@(xaYXc?^RF9D8 zv=*1wk~;4d3jX+r@SXCQuva3t9SO|$t&qmpMV9`+qMDd6o;SkoqLeT=zQmj+9eHe3 z#tp2AP1ns!YkntZUl$M+FR;bwW@cC@VJ1v%m2_x2OXl*cEi`3Ie(8PM!wg1ltN)Q* zceOB!;B(I?5Ms~$D_o$lyTCaN#04MD?@qVYkLbY5*InP{;tTa<{LeK}-H^Ate-)1O z>*AA5@-`^{l%~?qzo9T}Og%~$C7aF$RX#)>Gq}+d*+m#$)NQNHch{Zh-~$G% zoDd&H`|CZ-I3Du!F}Fj-(v?XQYizW#Au-Cr5owajQt&nZCzm}uOz##LhOy1%#vS=w7uN>*IoVH*oIQ@m9p^R?Up zimg|(v&kwy1LhKn0(#@V)Y2b*V)5}@8XD555m^=diGR=Y_gv3}T_Uba=9~iQ{m#Y0 z^D||PoN)(uWpA0B)$Vv5|AS8g`^3Wv!ivUPAJ={}e5i8?a1_HM{g&9m5VeT#J$=v& zM4^j5^MOaRb-7slCeowu{jS7hoq7QL$7=^)rc^z|i=BV?bZ}J{5hVP`V3YESNTOuW z-&|kkIj|wy4K?u4pUg5y6gbqmp|96(z5Urzl`?VljIvr&nD<79iJV_o?4GS6=P>?b z#5-zkr7?0HB{CSs^9#S#FRMxdzq*mcdyjor8d_fHq=b3)a!k5)y~b@CexM{I^^O?5 zz`(H*PT9xcy5%<=i@h`@2!CdWLt4(xq+#&2?e;+RHwXQQS(@lei^s|s0^hH`BH#1r z(dFcuM6S*~O6nQS$Ba1kN!nb7$gVVOZli-H62ftix4t)jj5uv2=Ns?(Q(VGeeN$9j zO8c7gna>qiuZan-0xW_yn}B0c(dH6K9iu+@d-#pM2VS-!ws~3?;`eVrXVFPOTd#io zKa5&O2v);=|HaswEu;PWus$6wyZ9xrR6W!)m^;c)q8}DW%+*P$Oo%IZ?AlzHSUx;s z5T`I_r!*!b63{Nh53ALvzc*qgYqW^--qUM)Gj3tg^^@h8ocfQwIK%UIhfrVIV42n^ zVX$T!p`S)F&L|1<{s?#@%FKlw;Sm!z(}?9#8Gra9RFD$1LjN^>w&yusURvS#N7mT( zv3Hla(Lb^^?|vnNIU-B{{Gtqx17#@ApUl zd3u&IGWCo!d;K!`X!=27QIb3|}&>|&tmZ!&8u8ahZEtcPK5LQ-HUl6nuYy(I3E zfz#!9mLLAdTlb`LJL#}zwaFqWI6Zjg(GBX0dP{td8YAu%%6FI`(t<(WO1q3#=<%1e zc85%@SE*;Ky1cI^s>`3Y6j8#sOVB-GeVu81V)R);_*wYEtPo}~M!oQDFMMYOedI?) zV0J21@MFaGg8d~_>9HdV5N`B!m@72PWZL%YjUxISg?~;@Q0%fL*to#2SiX&$7G-ef zG0%qtOH1v>HuET`armNOTSG!SaNayxenOLGU6C1c){#_mPFq0hvHtF_vxCNMSQ`oX z2A?8_dSDbU;}wh7Z!rI+o3*Fl&EtX{OAzFZ+4a4vQ((S&*_eLePCaU7+V6!mbfT1u zSIPA1DpW>j<%D?E6A0J@gaRr9&r8Ge%t=M9TgIGv7KvhAcTldO#5vC_1$K@6AGTc9SI5_?UMhevdT-mCIH{B1)I7BxS6 z1kYi8F<~p-6)){Q!?){iqqdHx4o=y)@~!;8)|nF33^M9~4oZJpFx?2Ks{$)&*kN5< zd*pdob&?elPS~o>q;SJ=FEkWSl#qJkX_-KjAaju`9CT)-_lE6~IMkxetm5`FFXx4| z^T-wHWqE+jZU3AuUygMCW%$Wsu68Q@yo`-XNvy0l!(4^yCVjpc)2m>4fCVCK9RhtR zxycmrJULt`?3J2uSyTrkGX?(x5ZS2SdmC<#aSA9qB6+yH{xpY9Wc;Vu8j+vp1^LpQ zA!o~drr?J1`HX2Sj^;UD*iDT-UV1pMKPEZNzyX)0P2}a7WH&Huy1L}6n?S?m?Zo)J zfk+_QVExy;RUK!<;bRsRIX6TsAb&QIL%f6=Vt4C0_KQaDODYiPoX*Bgv=G99c=rdUndtHzwCWo8GP{Olj3iD zXjWVlUIl1D*vqyYGTUlN??-j{vi(?5sar63dqXoc_Yp%xfn5s?E! zHPtNdpSpofAX(<5oqJVSOQe4EM}i8_iI!If%p&_Yp@c3yjvRai?NI!*gS5rpP9)^% zvawrF@jivp<0|q(gJ>o&1MX!ahz+|9%e)%6G0%C0Tl_Xv-jx>%!(%x5rhduaQs1T% zL?MgmHdSoZ-Qi91E;|#|-E-)3s^3YZl0db*J+x;v54iFr>VNCGBVcY3HV5t5-Mc<( z?7&wN=?;hCJ#pL|8#i+S0cf(&{O2b2xGC08Xx2;5*3)ah`JKAA_w~0xzkF#s!9Ohc ze`n0l#?tz-FtWc;G=lZNNw~JohqPEm5x7Yi$k16nL*7P2Z0q+&O~Z ztaEEI0ffV*tb9!zkI^sKZaV9H)el}@VR5PYVt9Z8K(iR6+Akppse7LNdBx1SpLr*P zb^YmA*<6>Su4S(F%u@`%e1rd59Q#*5yVJ9s#)f+7;&%BpaBg zZ6DFa#2r>hU}m17(RX8E9IVrA{ZJ|fGVnH~!k1Pf#1*RMZR1Z&fAQG{VA~QC9yC#t zNE_`jF;{^R-bBI3Ny3%U`V2IC36TBLt2HcriXM0UdViI?BF@RQ0W7_HwZe2XG}GcL z*jY_caDr`4QAUk^xnE2f@Zw#9QiLU8q!Q4!8$~P!W9oAIahHJtnHC$Xs?9K=Clsfd z5e@M8OLdee3U@U*68R0Oh(PqU`;c#6%t3wsl=YvUA6SmW_^r( zB=FW=__hiT-SmGXsbh}EKP)~h^F36Ys*@gFByozujQTN8Y>NNOMi$mzlYhB{fc-^i*({L?3Gz)Lqrihu3 z`=&iwuWR?7-2r{+AS=X20c#(*;I2yrcMHpVqZacJC57V>*~ek59vzK%g$ugtbG zKbRNbvymdsf1gn8Z~JqsRY4o^~{ z1=?i(Y*CDlRE}Hbd;BsP`dWFzp!1zg2YcxUSb_s0s#`>-avUr$!oS%~K}3Yj4Fa2W zGpQ>qlP8;e2X>pS~-(=Z9SP`+Cr z+Nno;?!T%)SzLIl*pFG$?n4CL@_|#%kz#|?R*-{DLEiB59OMWT{ z?{xxDiiNc*^sZ>)|L{Udr+PZlFP(~S2;?czWTY|p!yhfR3<%lyzN>gaV?78zBl~fA;9!fi}&9pG%^8Jf$A&ZTbF0Q^tN|?Z>{`5-?RnIlb ztQ~p%d27vcWqa=r;Jb!@(Dfk|2`UG#$yGj$GVW)v)%gwQJNe?R9+^*+^-4z6cYk2k zPoyVO?=dt1zexGJ5-6-1&L)B-O<%beldR$PcQJZr06LWr&xFpL2%fD3%yNX^ejO5c zp$i&(ao5NgtZ?n)S?7e&8GF)F-us2pEiA?9MkBMAct}sfG@xKqJYx-{KQ)5Uft2o} zO^8XgxlNQk=G(;#5%0qG=l(HYGlJU<+*GC@faL=DB1R6CVY*s~4yn6`ix8(SceHd9 z=wt@}6|4Zzu92;L*WE98bQK(TmVCT98X~0Bd_}gMa-3)S#NAEy9ypEi%*}N6SPf2- zqm;EG^F~-=wZa$0oSVg7tLY~7PN3W?*dS}*J`26s8(8yz~yR4Fe_ z9FQdu%7J+86JPpwT5{YV zW{_9=z$+&UUywwA?ki6cUw)(yU;q$&#QLwuGVQ+D9HPsAPc#2&Pk{5W|1Um}-|%Os z2tN%?}i4%3QR8sS^G&tFRJizzE0Z;U?NTRnq_980A0ZXY-x#(7c z`4-j3$@vcSBuxOHs!rUbGpM+^Ejfa?hM0`j)p7K>l1#JX423A)To9@=;1mabpQY=H zPkCv>4bR2vkKiuX6=vVro40EX%79jEzYc-%mNM$t1w*?JjVxOVN0Y}1d6}Yb?tgD60KCO6@Z@^IH`9>&jryO1Z533qy+aH&AW83~7rXF{}9V20{hv?ms( zcP&t-;S4FnvctW9sq8cZD=C;M$5N1QtH1W3At|t6V|pRsha=Xi=i5;RI11D{7j_5s zBFFu0(qZmIG{B67j%8AU;|7Ow0}F~%?m_H9hTs!T@ytLc(Vo0T9PYV zlL<8||0*e*`F487Gjb8oBS&w#9|lH2#8Fk<+zCuZg;~iOzh%+4z)928ePIO88uvvV z>{QhG!O^scNrSt5T{Y}xx`4o19{~rm_Gfw^EgWBeo}V;Xq@N@G;lmb*Os&A{aibu= zZKGuMOD~$UNB#bd%~>hlAh=R$4@-(pa+@a%tmhM5;t16G0jzDECT z=|dVxmcktt?C9~{Ym`79np%&Qf;@GRyAd5)T!9i)UWqUlja1YbNbpMEHNB)AEp0Z9 zqo32`-36#UpFBktSY_WS7ek)>3kJ_qmFW#aS{89%XY!%ByiC%N>lBUw&aMLFHcJc* zR2Pc*2Gci|8hss!Gk#@*nq`H%7}mfF|1R?x))d%I|6u`cXvG`R*kc%|X!z=aL!(8h zCtivO=N$tV8|!JRWp}F}isv2UH4e-sR?&0+hqbSatGZj-rb`;>MhWTeZb3@AySqVB zkPb=d?hfga?(ULqL=mLx{crF-`hGdj{o(MH{oDJRYt5QjYt}?^raJqJWMwwbO8cSQ zVQndqFowO;z~2;1NA#FNUZKoxbBcq>ctj!4qJ1j-WfTa{Do^X$%uo)5!KL@yF5lZ) zOu{$}Kx1Q|x0;}Y$nQm-w&V`En+!V^o`WcWbjO`iA7FKvd`rJVeq2ce-v5!SQoEQ@FDu=$c2J;nfS`&bXU^!wSf&3Z?xdKq2sa{v_^D!61;h)jm^ zTiU}Z&Tck_l;8|9;O^|0;+pOysVhP(^ATJ#KP7kQ8*=?9Rw6kw_jO!wD9mpTZqQy3 z_=|YlB3}QB=FNxC@8($kWWNjMoJ7z>o*&iJ1-uKYAY_#5Crt39xgu_QSq2)3M1m!! z30vF<$!wjxr*>ImLjI+%A)Fp)^Ab|bW zZoda@<)P)@#N9LqTV|O@IMW(?AH~J_RS4STiGyUCwQ8AAPlrMzaQ2;(RLmx?ugz%l zcVC5^(OU$}lt)23Vc}^fxM^yod}+)Jiat*3su|Yz4Mi_PP)jq>E{zewUHEo12aF{Q z=UO%>zfoH%j3q)&=*g)!-L2&-O1CR$%W!yO^`-MEL}(sjiSO$o?D;~ntpp>shbSxg zQkjw>{!2)&US*hm{}BX<)Z05Uzd~(@q&Lx@itH(MI2HHa4R1hgyswIG8`8RAePlpx z`rem7+H(D@4qe<^Zh-QO=vU(q7DV(P4n@C=Nykb^ANih8f(Zg#k$ zxOAO$9Z#4vn%D+6nc@r_(Z`=sR0MeI7#%_`*n$vtV)UU*&1yvQ#Y{B2S47=GUfsm zNCMT@i$1+-+jNIQF2r>Kr7H|ZjnAa1uojk&M-!6M=N~shDRf0zjzexz=$5F6h>3@i zETf0>eX79Qkj^t(u2SY9Qi#ce%U>HHx zfC#z8K>);jG@V1dNYC--&G2y(HhQQqbvl{tQC)Qt+E-jKWSKtBu*=ZpTErl*_9_>YanHv z`RS)X67}*?!F@tHGZiw}&uUIhuH%6BHvX?)B|?yRj8L(VWuj{fp2mq^o2)&H53~Pp zNJ`*0dzetV(wqSDR@hV_*fCVkVVL+|Y+Qc=_m^fjn5Tf})WiMyN%@#TP5|T?Al!Nx zK+-*mH;nW}#-kqF?9g)>g_-&`<%3*33($@lS?52WSl6BR&d#Mgj+M7|7;mxi6S4FL zQU^i*24UwA?1oz#dmBH>BT~$~OIx*3s%waHH;5R)zDaDZxVH4@gPsH=#(xL<3|$$n zC_?(MS*Ho_oWO(j^4$z3pSjN&0{jX+a9wYLrdethpx_1v#(BKA`#xlqpvNlHd4*<2 zmq0({@qrokE#9E8;+?xz>T9hu z8+n7?$MfV9O{$m_YNWDW*nJ6OqLX)3bHSP=Wh1-AH<(o_{PrwuK#>g3)QBaze-ENx zK2ePp)one|mQ9JIlzxco;r$_AmFqYF2`llrTx*OM{oUmdY^rlQl!v{b#~pYlWVDq^ zw%^KYMi3-C9NiiktV%pXsWf~#ujcF1jt2ef9QdoD{E>oYZ(ig-s;M}1=Bl~X(L1xM zTS#lP1McVmCDDIreT?J(g~17qQW7vx$XW+|CEx5dTZ#xRl1B+YxANsF+(uM?v31rQ z#=J@dAYn?phjb#IUe3C)wmmV-#8Io*6Mk@ngDM~6gd=}9uLpwU!1roX-;$Nr8jrhW{o~_C^76j>8(6VF*1zY%6gK*v#UCZrKi;m~mg(}rfU&Gr; zZDJk^IDRYB`gwzQ5rjFXBn6!zk?Q_sMx%U!2N5B$>w~vn7(P)w1PMLAzFrw|9EDPi z|BI`bNq-sF=}1x6(xlOrs@ZU?2vFw+(MF^TSfZK0r8X!TVuVU_EdTzI7h68ygSqdA zQr+_(6&x$CwvVnRwqAGEA`*XninzS$!*B`?V}%B9UVYm+@LUi4J<+5qF4#h<-38r+ z*Jz~KGN}#=6?>a9L$c=rj(&VYztsaYxQJ~=joT-b*)Fd3WemJ~wt(&3C$J&C*E*Aa zQAAs*3D9&yk;AWNzJiH?wH^>r(Egk{By^wQ;wHrniB`t>!=bpJpiaX9_yNG~m`IPsNQ4Nvj=RK-UX-l}2~Dr|o? z!EHw1nuA_dyqeCV85v{b2Qx$&eIWo+D?}8Tc6rpGq|j&Y zqQl8oj(uhhCYo5W$P-~IsJreNAV{yLZ)F?1cwQVYR92Y`O~>|suh!&>o|t;EN|=Q$ z-dhNQ^n2wx7)8q$_{lu|ltPKaYGrG42-RRJi9#|PKMRc9=U;gP5f*UNdeyg!6Xm>) z2)MJpyDIqsjOQA7LS;F}P&Kp#NdTs|yhNs4X1=pZu&50bdsl`0(l3I4Q19MZuNJuU zUVcOZVcJR%Vfd=SVd5=x4S}|}O_K0pl|V~9k0p8^eIs+XB=|2(+l8i~b%YPwV8#ci zdkNRQ*K5Q+=KTu#5i&28wVj|Yfq6@*D1@gCRzkCsQqUi#)Vz=mX2~wYG3>Xj7ruJV z1pxCGAdHzsSBOn*$T$dZ3EB)Y97h@)%TuEUjg`IGoR~Y8`aqa+Q0$#8%F|WFIj!{) zoi=x3@$-*R4i6DHgnh>Qi~ITh!j%3-Lw&=yg1yw(l*%LI`7*|v%5#0wdI^P;?jvu9 zi*O)I4YeuvPR^EyE3Q_0Y#4OhF-@2m>@H&mIiiGxyu(!fh0AwTozMPi*)g2D*O1`XeL^OiItrV@{sY@9E z(lM@KK64rdBKM)3yLbZYO7l9AyGkx~vp0&C-ngFC<48KUODFZZl0pNFwPsDBdoEK= z*YOg;Qj`kCT&D(0KfcsIkce4J?B7-O5}IArQD5p^^sLpv3S>AP0TDx+K+6<1;4bWR z5c*kLK=do`Xq4BBN-f2agXSgIS@-aKd{?95j4r+6(aJg(}zr!U=iysYw%wtI?dg9P4-FuwQ@(U zm`J8#RC1R0$ItV^<d*jwN6ILG_SkXS;pLICaFQG&WfKj!3}f|N`Siygwx3Qs0*maf-&q)1+WZ{uxbUAg{aDbFtN5! z!C@@z1u{>2`TTrukd`?pwB>!qT`8xLRgZn|aSrlyzb!$J0c(%w*p;xuky{wYWmbTs z7@y%imdTnu;?4gh||vWl{{+^4?;>*;}`J!eB;A zC(X(&BSK1@eYT}1{pDXgwGH`mPID;D2REVk5U)(rJNh_?4Y=PuBvnLT(PDhs2Hv7e z!9^VekeE|WGt7@|Tduej%KTyu9|k?4jgNMZ)4^}qv^@J*twA)!tfnrE z7i(|gIVpQbD6)vdlB6aVh5Na6j+siDAX)$2yE7S_4;8F_u%W^duk7u0i#+YE?mBOL z-|4~2_W}k5U_cQQPaTX)nm4o+x{X~{aB|dc6fti~zqhPWA44Zsy|?)WfN6(AYs;Jf zQ?MtRCvqA|X7|_hI3o}0J3O=oXh>xndT9_Q)i5e8D|#$9hpF4a{mcT-syAWqubn;T z^6_mPO-K^U{_HvyM_<(URy4k>+^v?2{rgdYq_y!T=@N26Ui0sG)HHp7)(XirtL~GV zpC5>`NhIRTr+A-FT?xV&Uq+WV>TE9f?E{c<9NFs$;#+mHjep3K+mL(C(h3lCJ984} zLV-6Z$CR^xAT73;h(?qLa7@m9`G8g`W$BE)U#FrCey3&qZM1Z?^6?$-w}!_GM<(M& z5xX?)iE`+NS8ICjxPEg5XVG@aL5Cn*&rE?Jkr(3x40i}0Y%MKT)n5}qy1UA9PN zk@?`nz)nX%!3CPq2hXG0j4{RP3Q|fucfL(&&|U8%FWoPh9HovC&I5*kt~o4CK$n4( zM<_}IA&7T?i1H(PX+-O77Iepjb#hT82vRZ}w>b%C(XxkQG=5Q@$^(K;f>TbI)=1G# z`@-UGvf7`O6B1qUh!8?cdVa{Mrs8WoH!6G(wb>eK7);^Z*)H;761a_>Bf1=lZbVf% z#}>~lry}4-yUSse8W-@5_X_c2`ilaPcGuWAH?Ak|`N-O2`YV_%p$b^;s$V#L@qm+m z_8qZd9t5cY{L7}wdU5{qEX0UexC3zXaEA)$H016Mib_Nb)hX@2>_7(8wGnq2krrp$ zuH1O92dPrFD&x1Bkq&6uge3PnpA1mlF!v7|jF{<4?H2@`Z}59E!Q=VkPE&%^YoZ`AJ=nx(@Rn_(T7 zz~F(mDMxJjz%Dhg!HD-BeaN0RGGIi9ScYBp!n5`Myd8mcDFCJ(^xi?LL``lr0snls z#E^kuCV9e_7AW+kLW);`7dKx)n4syGyfHgu6U{#%O zKh}Z&*6_iem0m$&!PJ>dhrWOk2{AjH8mi(>(U5@95%Aow6*LGECI?iKg8Pl$(%zZL zXBta$+ZNo*9fhtXG|D)iP?Od&0Zjgy>9wL*o7#TwP}x3+LKlAjE;eNx?^~WdoZv+5 zj<^cKB;)L2*Whw1FAI<3cm&G>5BrceP;^eYc;DItYr4_>m`?O`B5)(N>%=0R^#q&J zgFrZNKeQxEhm&#VGpA;MEv=vprU601orqC}9a~_HIOSAPE>y{2Z8_-GMR6^@<49NK zRjRWKY&r~gC*rb&I7Q(fzCx^&L{|7%@Zypcg`4Ph4|z&Gy)+<5;%qDf^KGNJyHYx( zmym98ycsPbZy7XQ@Jye%_GKuA|Jih;0kk{k4_;&ErOw72hrb99}NK#=Xg#LW65-{QwD@sLCp^P%6A|ZS($hDaeF>Pp@sL@!C!iD&Q>( zNAbva#tED)_&JIu>68{%iCg~Y=}00cuo0RpPb1BYF^z&r2{O}_aAXWWUT z6#jvP3r_i^+60WoqS1q+?A)f|OX^WrE&sK5L3y;@*lT`mpz{8g!&>jh0gYGCgrpP+ z__SiQawK(eEmS{v_yurZ&w)ny{=6i`dR1jlG9X0J5^qr?yxsBzwn)>iHw57XoTf57 zAH|JdxM_nl^L7YGy98+DDa-xR^g`43F>0)snX$^6SjNEsxR{*5w zteI!D^$Wx6FAN4c!%bl&Z!D^1vX z_r>#V^M14&g*5vXfR^q{zSmw>w(Z4SsKDV%lk+Oc6-?F6-Pr*b_h^?=RgSB_O!$nX zlgYKmT2BRq39MK0eN*^&hIZqwB*IeSrsU}%Loo16c6m=eE=-gdjwMlP5sox;u7XfC zfE0tY%I!e!sr3=mm4$&4o!vu5Qo}?oNo~UloXtrHoJH+t3OS@b12M)am@5dA6{%$0 zg3+xpyHvQ9H2sf>!Im_;+%t%`@|%f52?DPl^KYNbCFov&gPncn?zhd_P^M@!Q8i(LsO0{p&%)fu_3tUiKYgl3SSVkIGee#FuRG`vD} zfhr@iBVdEq+5dJfO3<>v9d#NuIMUD@pFYB+*AVEpKBMqby7hB{lA!D{v2> zev>$FMhsCWEl@?oeWuHC%=h6tdxV`pE_DEbq1!2DOeyzKkHm;4vQ(o7d_i@gB^M`DJzEc z=2cShcdrxODF6~m6Ga8&^YhjcXtJH~o095LJ^eEr)ROCl!BpQ4?t_7U1L==fA?riF zK)+H?QEiaC>FFc=p{U%>u)aKTyH}d{uBDI26M{taS>r=Dv03RrdJN5AVvJJXG3mFx z7zKD-81P+@^L0>m@%R+`s{np6vlW8vn|IxVTh&xDV#M0dJvZ^_%E?68EB*~6P*TK` zGKskV7(Qbzztv`4y|?t?)>?7tX>xrCOb)`Wn1K=F1zv%zqaB(b6c;+_D2!vckc+Gu zO;*krG;8rHhQ4sIU~vLo1v2xKbB|^}Z9y1ml}E~6R@l4eW6Q?H@B&12b_JUufye&_ z64D=KstqG4{m8qsNT-cecg~*E&DztA^T6RXpx+FYQa9N%20?;c={s>ZtTZ_Ku1igw zNG$dosL8L$M$9;P#P&(pvywcI7-Z0ZlImR+$Xzc)LRG+T7= zZT>fyKhn$a7<)Qs^jmAy{d(qpUyhey_acSB6{yXk07p;E+e_qT zlKqZOe1hmD!VJz*_im!^=VUH6o_-`K6b>~K8RTESp1F@H|I!k;?MP|eQ`o_G;R)b6 zaANPFzXL%sZW3Hl7>21PM{%~6! z=Kkm#gWLlrq*SolCb(((Xe(9DT$dzZzy~H7+VT%46s8W>D)|sqo_#j*+QT2y2&2yG zPA54R0rJ4geU4Dab7(uygmxeLEKA$=GMh!D&{c`Qgq2Qu5w;=z(a){T&WkyXxXHe~ z`*u02JeeYJ$uex^((ZD`lZwQm?cWA^hkIg)ro7N%4)I2qE>s=o<9Wt?hY5Gux)#6v z5t%znf%(oc@CsLebk>crv#{PglFIMh2X9mf|LtW@cPKt8^=0v0FA&;+oRV9@s=_lo zMsi%{4A_YJ*|=xoC7)42G!D~2Qi2tS!+uk_}z@&(U zH{U-Cdo?<+Rcp=Ox0IN6CDY^Nvay5iDjKB0eDZHFfikF`XwtbS3&^7M|6CEJl%I}u znf0Qn(-C%0F#8=1m%m)01tCz1@z(ICG^3+%EArN02IK68boo zfHGZP`|QLfIy*LGl;?S6ZC<}!)JQI>4-pa`n7kvg(*g%ULX=%#q%!Q~ZBxccIc=)p z#QoT*IXH$&ug8ilAH0ms{+H`5_yFJfNxxV7m^t=UVNi*o^zhlQ>;3Y>Kz;Q)5+qX~ z1>KIaHHFh)@Y$r*9a=C%l!KSVx2^Q|3V5Fj)hn>-Ijsxu#P>L1<}KO0o_x8F`6eK>_&295JMN9k9Krj(+u*wfi`}lTl;ZY70HlB8=ZwrhhSI)veZ^eC zVW&1Mj$9;M@5O-g8U7pLheZc(nV2w;q1`NltqF6aAvQMTQ1`PzBpT^JtGeN1`7L%rQVyqcG=M4n!I)=S_BzX# zKX!wHQaGItD@6k#-j7xv?z_0Z`2`<{rvD2QNW-7BNlTBcj*;I10{ar|+P$nG9+j91 zd1B>;hTHaQzKx%)Gw{}Plzx^=<^-4gVs_qx%u^{)FKj(8~<*qBXFx7TX zO&$m$LOqqI*kdU~XFlQh(^m*Wy4rk&a}`G0JumNB-pA*eI+E0FQ`nOi^=`$Gs;Rob z9rKHA9j+h}fs~q|ah>iD($$xHpEL|cUC-Nvv}y|bfVUoqYi;O}!Kr;{{++GF7lVDTD8! z%B_-o>%;fW#&hikX8Q9+>Q|?$c)Q}qmQdgo9GV~{2IbkE6>+m@y{}%=jc-e55e0G8RA{*QT-M^ue0zQz5F~KqyKCLSeI%nRB3_K z65P7r)1S}44P9%c2uon4uH-Rn2A-pf1FyqBOFI0*u5!23Ba_W8X5Q=m`q~Ot09^|I z)ft7I&kWQdH|AcgdUNkS)HzGog+8=~n|Sp)MZ`jf+0i!4$-YDv2SjMVrYU&kL<6dA zWN;bg!T!5%Wo|>iTx%AVCM6D%;IVS){}3AFORi_pfMxP6CV-Rr)nMoM)Oyuc2@NXTfv{KIWJ#C7I9Wg}D9HJ5FoDx7gXnxp zrjVf}sK@mEo}(!b73)9NOA#TpKg(w)2UdMuhJ}TrU3cDb!qZ5 z5k2pSI113${8orn4%;AswrP4z5G}>b8b*&#BcD_m{i*X8@A_+gb|4lWKF9Ge)bX^I*DVW>4)x=DcniYxWbq7>p#8g$ zjTgV}noKsJQXpDnD){MPXmLgCG&I$bO)vtDkgJvSTQ>EO@gf!bz<2d3SP`r1p|IXT zX0XH(HzWG_{!2)Z@%xpHNP3ng(rMbQ>?J}P!6}J4**{%P_`{t{VAdX|i~pu6Ki0zr z{StmCvS1Vw9DBC=hMh`hF&5_wUH~<{0GWsoa49RT!;yrjb%~4kxOOCd0>umCy?Q~b z)Wu$slmgX8_u^k-g6Ibw-hn?*9f7ml91g$11sRLg^CNZlnK&eu1*QLg2xAe(iGn%Y z#*s;B^Fx2a&0P6a+0-9J02ef~_AB^_jSx*1rBxG`rlqu*VEjg%vIi__vAnuTY#8)I zPoxu4M1(yE&9j>{Frx~jjfj5_l_+m9;?hjor&~QgU#Ny4F(_4}Y8cK-U{!aH>C?W~ zPjx3_5+f5Z8HJWiF$sz57ZO^lE_&0hNke}Yf_%3-p z7D|*3eh2;Rqk#8Ilc%Ij&0BVhm{)?+-coe7)bD~z<3)MY|M`T?XwdgB&u&IuNE)N# z3ea1T@}JB6Rc6R%3XLDF@r&;Cq29sS1=@g*+r{z|e~3=mY{?PwS>hn*Yn}w;bKe{N z?mvde?wR-f&CbMgT=&;}5&RwG~DuQ#*Fw;Rk-EHQl0ns)t=l^2(sw}Z61jm~`OQNT;(KUq&? zx1>K)&C)E8ggwX+plB$-e`x^&^H|f%cmEM^izc^%-87^!MKLpRFgqcAr5L{HF9FX= z-S~KPs;X-g23Lyh6cOK2ACDc#?5D8daV-14-N^jol@Sl`@p_0erO#rsjiya592l8t z`xAAo*zw7)w1;~_=7F)nyaxvn>T{DrUC=N{fo-Dj61td^lEZngA z6^rzB#zXnL{tXvvliW+*n`a+Yh2`mA01j0hmR>@P7#TU`!>+;1N1sk4YzqNNNaIiUC@68BbM z>S^ZL0uVlujIb6oX!3b(we${yCN%#&er_b4Kkb+Ia?d{3&G1kx0KO|QFh-4FyK;NW zB>*i>knhVa$22b`n6+?yZDpsLQ}g#n0l5?ZRO+N38^PerdF!-btUYN#smbSm*1_ULfbkxa(bj`V_>s;%^uSn z2RdNV(x^#{3wx2iLp15AyUcQLW3;%wtqv!B6>!T7bQ-Z@baCDOK)#B28Pm!p*_udl zEijw4`m8$RDB3ums;laEnqzk;=yvpY76@^7e)qGW8=~^Al48c))%e%F^=?q(%g2_g zptHg@EV1rlOFZI7M*l^-_ntlPjU8zJb5;Ojo`XnopgM0(<{5hP_Py_#|g zR$bZW_)T9_K}z?VOF4(bDc(Ef&fG@u{V!$1Hbl#7(FNMN^iU`QZsfA5fW@Z&O8qV+ zRc;#0F)ec{{{p!q3(}N>W$?$4r!%?uWz_Lg%BZ-lsn^&KkF~=sgnlZ@Ngvna{(TZE z%e0;ert`$>3ZODU5uCiYm>eNUtVM&TQ-TeGP1sR@B1_VP~k|F>7}8i$$$( zT*Pxr&p!M49q|N_*Hd#<#Qcy{UhoOIu2YSqkRJI8lA7;mtI2SQ>ZFuoYz&%9?o)Io zizGwKlsrg;{eE-V@i+_pkxPNusDhfJYOP7)QgsR6l{p0@V6x_iq@SSk9*D~&bbm+B z1^yPIs#g(4Qf}fs8rGlwaM?-=ojyCf?6r?@a~Ih7p4rvVJ&#&yhn#W8u?F|#j_ofl z9rLr{H*onEj-F2-R!a%8W6yv`yHjAf<1kLbSQn@{0+}z&FRV%wU*t-KZ|R9Vhe)bf z4eu;gPtsmrYT^32j?DPPB_2EicRr3hK|*|f0M0GA6^r9?{Q<;6Ws%l$M%@ATfid|@*9ps`5&Z|mMNvKsU6U~w`Rt*sE&|y9RxhLcK}|v8A-(Ef9@S}J?*d~1 z{m7FMV?eHPydfH0qM=iF*I7}9u1n2oh^2~wXncSa6(_Bs0QxH`@uDcAa~2)9lE?Hu5CsD z3azL8b(xwZY;kw zRw!`mg?Kl;A9)|N(QiR;d8li{rjjX~!lZ#|ZboG$ku>b4vk$`5Q#5ZE%1Rotovpvs zm8BXuAs9btdYB!hOrd|tt`-9d?*4NwjoffxA9a?Q5JLZKZ6fZYI^Q1AZ@hhE+~zG- zXnfOG4m>@X@v^Kdq1omj^O2Lx%#a6gZ;xN+VXv1eLE#9maDx)S0@?ac%d%Evxf{%d>IPOTMfQKN@=&p2rmICtp?0tLG5fI{4X{=Ct=-nWGa!6qp1N z%=V}4f%rAKeK@Z7YG(X{LLs9(x!&@r*q?Z|G)rD|FUT#-oTr7o~9A0$WPlK2S5Qzv>ffaX2)ovuOO zpp%fz)ln2fq1LI~6c}MWblBl7Kkl`7$69^(UD*(QO^ftd)Ok~Dm3oFA-;$lHJ)t}E z!iefvix)aRDJBg_N6D`Ac0G@}u!lsFR%p@WkGYsV5s+$*zAHs_C^>nv@W0(vIPM@R zA9oc|=)1lSxG7anJGp`Tu|2Rp9s7@4&UjRbypC$#Jf31*fMY zje{sZ6a~K{t4cDqJAG_D^_xsR<}iEFUcs4fw2s%j{3^O8-VCy0U;S*HnTUAHx_g@{uClu0EJfCG>ww zrhL~US0Hmf&iffdP+krbAMaaVz1*3{K6Lr3AggZTe@A94d7+?K%Q*D<2m)5j*RO#x z&AF*&j5(tgip1j`_sBxBOm&*`-Ls9wy)5@db8?E_-=xXD+<8?T0?EjetXhAIUc?SxaF?7jVxRdjveaEMQrk>Bzd z$vJ_v;u)NPKG=Yn!ohtP4>(YF^t0Np?K*Dzqng1BLMx%$`gP)FJSh)LrHqAMaluoE z|E=$Ar+hlL0c7@n_*%PA=t)TFN-}Hv(HSrKWZx@j!ba;Kd;i~kC)vgoBI{(UPW0^B z5;h7~5{Up$l4!Yz>9QhnWBDE1K4?k)A#(&a<4DpM?fXmUdHDF+5?8x{X60YcNkZlt zkyur=eZ3$^V5=+cYf(k&YE!rOUMWveuQ+7K7jj@pIfGFK)M3{>Jdvqjt9KGBo70Hb zEaohPLw)ahJxYs{7hyx$lIjA4lf099BTlcWk`A`VD0$NnDmqyiUK@VO;jDGm z2*T6a{oiWlX4IqNfiyFPjX1Ka3nR=0=S-MThtz=KvhgIS%(n5-umb=8@7sKUk9&}& zu*r60+KGsd&Ec6g$9m}9CBr0vk!`b9r`qFemM7<_Z@k~G>h^=(Hhj(mAMSH2_!^wk zbOV%QGT$im?gAwa1j);RGcZg*PqLsZ4(Zqgp-y4u0k=%% zVIQt(*u??tKs*}XWujxTEX^;uaXvr%Q9{+F8F$bRgYd&0_WRuT&Aj^fok*f-SmK-1 zT2(`EDICt>e7X1}|v~jkf3ftPT31rzM1umfNVX72T_24J-hE+wU)u+7i&vj--Nc0O`1f<(>>d zLVfN?#A9Tm_sy_s7oNN-Iu5bbU4ZC$Qzjk_yah|0As}Gnk>Qx^O?ZC8O$F}PX8C@f zFWYB@z6HT^2W`PD7=(`vMcK-rnCq zk6VuvJV(XiAkIeEampAAjJv_#UFLn)|CfMxCVa3bMa^@2(n$lw_oP8Xj_-C#GeyX^ ztvw8D5TQ~)U-Qor(rIGC8?OrUQTBec;aSmlu71gbh=d#A(XcmL*>wWE0u$^_Dfk_J zERM(@Hl4{b-l~CQN>Edo)4;Ka6%o^O7c_Z?4 z>Nf&814jH$v)F!B`+@N8HjOpjzO-Msjqw0u2Z)lAqp$gl{H; zUn&n^q=?NnQ-2Pah6Kb@@t_EmZRB#=;Fq!YT5__vK#4F3RF}m}jAIaGdTII8k7{ag z_E9|00*X@fl!h{KxK!d31#BhWJS@;l;02dPuIH}&BOv?9A$*Ov$xAw|*3WDrF0gOI zFfINPkmUp1ZR_66EDyx-(dSwJK=Hw_-a#m@bn+tlB}eA$CeSbaUcf2N6MUpsi5nkV z(a@QbJ0jKxmcYuvxpX?eVS$xjwg5Li`36~P1b;Wtz}~q$18t^TR@&R%xyy&s(5@F@ zbpa7DfQsU*%I>Ps9_2(d-u&<-HgX`5xK|&LlB3)5Ix402O2&QMgS#L7pMx|IB;mbb z@l}5ay9;T4d_dL%Quu>1)WITMDa4#I2L*m z^N^abI41O_yUrS)40^`?%@&Z|Ni3hO5^t_kraI_b?2}Q*tbNL$`!=N;tm*}JQ^flL zU5xWsOR?!2N_`4nvP{R)f+YNX_ZQ#QM#1Mi+FVNEI(r4PK6ZFDZOjrc z!%bHp9#C_HNCyP96I{Q8)h-6<4g2Bnw!OMZVefBZrQ;2YEDpnb$s!8(+vVPKWW8=O zO~;Pa&^Ul9PMHrxlaBH%;FX*%h<(wI!2&@7=6 zMJ4ugr5xy)|Ei=X#vug|L%0% zE3!V6H_)Uh#t9#sir82?)fo|Kw%-~pjF{UW5|1N+7UiEB>}!mTd|qrL5o@vLPNTK< zMN%OGWSqquiQ*#g1~&6SHK4z?Ubs2YMYW*tLA^Dy=5m9lLO3L)$7 z!*%+vHEv~wes=GlGt63n_IaMr*9P)d0>gbF*3%y_$bn{u7GbeczB$&-LvI@3$|Q?5 zKK3J!5=$M#*-C`x$+2|fJ@QFk3N^o^YN_R1^^u8Xk{EMuAyk3Xe1IA8rmpn-B{V!K zWX#sLuLu`XbkL2W{vy>3rP#b!IK8wLBwRl#Q@u@3rhms$+O>Qh8^Qr>O%UA8vNvte zK&%;}4})hPHV#d%1(945J(iUM|4&5|MGE|RO0r*mMFfMX{EJgY5&KUp#pr^7_cE3x z7K$dP-4ieRVQk`Mw)WZkX+xq6**b3a$0N*d-6Wm?V}Qete;c1> zpB;-Bq?M1L+zk|h;uiHy>^93R*vw#S<{vM~Uv_pLIMyV?%u|qSV}6Bx(oMFcc?qtw z>`lfDOtBf1el;%{l*4f><@WjBEUKu*=g%QXAnr5rkJ7zo<~=RSHW_LNCwO;H!Pa78l{X6P4EMv!j)W|MBg!&UaaExzXnv-`c?Bq3|b zTf*&qiLep%Znu)KqGbpY4vY^+j9!eVjTQqU&tJt&}&$uos2ebZHf*%pg&mt@r0 znW)nC#HMV6`Cw27JfEjrkehNM*6Zu6?}RwpOi4Y)7+{RyjMu=OU<(nGbRD~;SCf!h zJ3#e90B!8c-Tekki$oW`lJ9BX|CUWg=SRhRWRnbxsd1kwJ)8Sl)5bTAyB0&fdRWoBF<*ig+ngg^F4u)bU;BFDK3p%9@8X4LL1Js z+wHKSW}0OJSn!WY3K9nwMe5l9v2I$jKeml~WV1XT_1BI@Irn1Bo2j8=435i$Y@OiJ z1gX@2GAaJMPoEOucd+OQnEUxq*YMF&20WwQz+$oaZPU-!KZw80H;?`8({D(muIeY8 zKTaCJg(R(-=YP{is#NK1!oFpYl&XNa0G>m&@~3}t-nuzQdZ>X*m+i|ecS6kWj$mW= zwpb2`3S?dU_UV%!u)FiSv|sUnxdh(va@7{&Q9U!N+b+})r*)z9^hk@|SQyzWqkWT7rePE7S zL(GIH%DtBTuiu)Yg#Po^^tDfCEdwJv@kQ8*{atOXl4T*y)qB#xMt%_On{L-;I0c`|1{RT==SK)Mgk%wQAr}SxMU$w(fv{8~2;dJAboKDCU%w9~zZn9Q-5R$D*Dzx<1TRY*Vfz^5G1-U z(N$vvbcg%8LkpY2%Y>=UbYBoyHMrP{VAjgtSC%}o`9FJYS~F$)XD9o$m1tVmBh<8D z9dW60@r-jHd~yqlQ*8TpKE3BzSBqiS_L!)!g;;Vb36QmG+#36y-h1e)ZEv7wg9YeU z{yA^9nMzq>lD|bMl%M1Tcc+bU_QsjHn4h4-8z4~*y`lz6z0|i4eK)_;a<$v}=RK_5 zNv%LkOBT4EG2IZI2`S#aCHu|hRLo0^iJ;VrXs?|Kp=O#dy%m0%ekZ?5olXcp1L3T#aL%898YdJq;K9ULYGOG`nTrD4v?b;okk6bS< z&;{#nyUF0_!F0!+T;bL<@>;FyXL2QbGZ;B3__5*2E><@nhx zT~3-@YX^sDWTaBKbA;fxJn-TtyZNRiQR|dr6@0{Smyj~HvBn{+5eE0w3I?P>qhE$TJ5=8a4BJS$YXd1VEeyEb0bMJHe>pU*Uo=5;B$_TU-R>GetRo9 zETq!Z3NNja^mwlQeRsZbaQhfx>Gt^~8t3GL?Db1@l4q&W+K$-!o7!M;a!?t0uL2wMh5>6(SM4X(XQmhjSnFMj*oG;nurJw7iwTMg&XS~x_0bEZjqFXVoE zq&sdLhcYHF_s;&Y&K4r@$&5Y9cP{3HT$m0S?Ms^a-4kIiGT4C^7>q2R_qg>QKasC= zk?eRkRvRCa!?+e@Q+UDt%Z799fQo_$Yynpq3(C4s+ft^ z7oAa?kJA$ViC0&*BOdIa5SSpxYk5$iY^u6KxK1s^g_sJnUTwS+d^m+5QHG*yxr9+B zbUBzrsZoDR8=fxQ1JCKjvNg=5J42S6@T4%53$+p^CD9Z$tyeG=!nY4q2P+>EbG={a z7#V>}l^y|cZ*AUG9M?dkIobn5z2b(!!**^55qC0x{3=SIMXD zubq<1fqg5rlAf#pDPS+cvcBi4mJ_rAf;WLM;o9=g2| zk^g{!S`$GE*(>3J=hf>tsF7P~bRn&JK_{4#CKs(dbi|38( zO%+ufP^=>_v18{ocRJ-_TicM;4v?ln`tpwvu2Q?*9}@`-7HF%;FxfPe*O27+{B};A zMHJ;W6iYD(0!3lB_Qq~;gLXC540WBATB!BkZ4(eOP3Di8cO2lrK%ErPv!%hA>Wj%*6(L9n^-uHZzZwtOlss$l{-ht)juvWte-Adb zs1HTGoYG$S$bs3>S&jY=aI2P9m<_tNI%zbga3(f54i&aa*is za!>^V-_55_p8yO;J*~fzl|DJ&c`UUYbX8|eX0J!opCba9Aj3J#cY>jWtamUf?_a+l zWZ0}H<$->$>6t`8#zxTs(y8AMlzg!I$~N9qoQKPo${3LobI%c44Hqr9CDpOqQ_z_r z9Dq0WczL&Aq^bEqT1M9MeGGecIi%Nb)-7u1>~wqAEJN^r)0xT@Ml~axtEi#!ZOA@!0lZI?1R_Wlgr1W`QdwDI}`uIMXYQI;dr;Z^r4o zNP%VtC9g)l3+h-yq?S2#wmbf&6D;#ju9TUmQ={t7lvTdE?)OJ=l)dl9uYfQ+9Ln%N zF)1M@mZ11hu#N~WyZ8`CoF7hf{@Q=&tTu`GMGXwwjqWPO!TZWdkdn4wIW-(Gq*_E3-4Q%a0r$P&##eY?+&OMB5Db`?IR%$?BII99 zc#Uk5tf!%c-C6em&-=RX<)WoQvTmLDvn=ERtMQaspvnP#4?ySoLr_>5+VW@e zKIT!4)T$tzl~^%#TVefXl|^6*0dc7c^q|yu9%S5B}xIaG1{MJrF z4r9Yu)O@hEkS*)I55c@>yx2s>B0j3eI+eGT(~IZ3=xlxE90H~1De{jYCKl)B)a zdofhJ-fP}kE-)l?wg0W14H0r&>2qBpP?+;f%4V{djWc~p_10-6A$a{?Fn!#ofvD8` zXd!~xFA_nN7i?%FQi(VnsTNbaO%6W1&Fjy!{16}M{GXj)<+1)mr8?VCU%FAI8@!Z2 z{(sDURali>*DfqT8l=0skp`utyA-6m1u2nkDWyTW8hq|}vcPWo;Nl<2f0>nfyE62#e#bcWdP}ie>T@F*OO;`y z+3Fj)HAW7FlB`)sR_Y(=tU+}0d5Tr>G*FE*3uoM!232)C$ky2ESCnDct~~!Fa49lV zKqR0;Ho2vgeBwHr_)a?|xn#de3nnkh9DXnXseJfOI*lwBOp)o@QK#tx7aQZ0Ob z8u>DGe#03wJ=4e>{^8Xm!@K>xNEMauZ@ck4|e zyNt#9`M(S2LmWte3Q=~-C$nO+Xl3c}iB|}k)3%e+W0(TL#e);VU?BecjR3kzvvguU zMa-JWfOmY!lRgFAqw;(wnyO6V7u?n(+Pphgbhp2}-qtdKb@>RIc=h4yHn`qNn1O8d z+)mPU?QR$w=SiJaw^mgkrgqPDZub=Q(1wdqBZ0nohx57o;|FtEadSZH!!bQDgd6wQ~Iw1XgZ6w{_YifxZq+%ssC)#{NJV1 zPwEOCHl!(rG}T!Aw@51>o0iwweI2DDy|HvizD6hWjeehGM+7DnGT|4wofW_DaC`k9ig0et=%t?g7Lhih6BkjlxcKczFucQq(Xcp|?}<7Gu; zOvH=V*lL+(Z(ktj9Dqf^EGk)F_S zoD?ut6dzqtG-|Vu#Uy_c&|mAvHXHY0e8zN`{dPbCf9|Mx@7b9lGYxlfr{1a_&yJ=_ zdUmZ)%(u6-AKNOAw1bOZPYpo?7_gltHxVO^+-0E-5%rtbChJgJPNd$yJ$p#ldXQli ziM=YNY6F4F$pSL>cj0|qCn}hxFDTQ{oK<&lLQ?Uv&QVUZ@2TcmiHl>6%l)t%d3yZU z%i6~_2HUXakldz$senBAe#(|rWTwRc#~-3_i>k_Qo9$3RJFZRLi}T;ih3{==ycy3E zZTDk-cv)y={`m2{%04LBRRn4O2Hj{A+?taM5V^fWH2WZH7J7|XWWe*>)UmTUl?~V6 z66({KxoCfh{A!4FhSKI~vsQLH5i2k~mrqA*>7W%UCY4umOSKkce;=^oM*%a6TZ~AO zeh7MH@#>%h*8W7<5~QOD{=i!#fGob#kaG$_@ALnO#&TOQSB?hf*7 zY69$SPvb0@)eUFY?1AEdiTheqqW;wJq(hoeBP|QUuX^@oC*5K?c|*j!=U+l6_!>zcx;ZLlrl8dtnd- zB@c}pj^yLk@?G*2e`;CcPi6noTbgNTO!{jYy@&s&SF6K>{ZAWz3~SLlt&c%JZ@W`=XH>~&Lh``k5`vg0d3}LR zy(Jx{u-mmWTW|PJq}z~e)59!oP$k$6wF1yOZqCP<|-<6ddY)`Ao>_Q{I33C0Mi1Lcs<7RUvcSRD^5PO(}+CIVr(JZ^Q)aHn5 z>+{abPwnQ}pQTIfbf~ibfYX4wNm>qnhKB)})B@9Vy%$3Q0+ ztxeJpGs$J6Q&c(&1E}H`26G!;ilJm(;DEKL@qAwr- zpVFzqm&PVMgFYG9l2U z^Vd66aay=n=enMzgdlRn)RINE40v}ZlcMtOc%0I)!;iX0=6SxDn)>`u@y~?8aDx7| zlGCe4kS-C3?L>)sy0%3bL@T}-$^D(7)&Z|70GL;dOpN)vq7$ON&$ZeEv6RD?J4(#*Fl0q%nYLHi4?#s(UHku|}V%;=?Ju7wJ1f>d8Yq63@(Ds|GzFWrx znu4EdrC#xJ*lKPv^<)|oZ=QJ;E$EG@iwY~)S+i@)J7L9ebcd!~miey)hS1eo#3ZJ1 ziqUqen!3%0Qsx3-6^vM3HML&=$L^hhXOF9G=PUW?Q8zUC;CKvSZ#pTRwZuxQG`(=# z-oJ~c`Y7%{eny8vEYKA;PISYX@eD_0)FH`Te>sTI&ExN|wB_#fQ%x{ckZ5>bkIO~& zRRr((^zd`a(Ft*sai6?8QLt!ELrJoWfymUzk%lrPOH4x1Dp6OyZ9ZF}=a+YXj=z%q zeaQ#s5wLPef2%>t#vLV0EA(SZ@m8j#s-{L8l?Nx)tdvXY{NOGQ zHJ!1Od-d;P`H!LlYRpJJTrsV;ah!u;c$G28l?jj6CL7dW04Q?2!4;&9K;fz(b##*5p?9vX5ePno`{M;3YG zKS8sec{weTcOBB?*SfquxR|ii@$b;Y$+-=<4u$5- zmsdn0K{2>EFwqozpj{}ZGM9-RY2TXyM)RLM{Pp^zlRN^Ee94)W96n`bRl*}vILqwp zrc;~8aW$nd0ziFzn7(AA?d@z$qRyitB*%^y8?A)gCO*;Gr%7Ig7*Df!N)_=S+8z>)U-B}@>Nxcy(D2}5bR6NckD zAewq`lcGu$ipDumW#FV@ml7_Ll5vO?04Z+Wm+wB6PSKxY{vzsD@ixUOT{)69Y|S8j zk>N5HtwKzJcK8s$AnswdPN2reHnPxTcBq?ba+D5~YBfzE=5OC9#A`>N0t(QoyQj&QK;`y@aNf=vTH|_SI3OT6d;Uv`HMyT%xnW~x<@qkKrkRhX&sP!rxHP03}70B<1$C_ss8K4jQLgJ26-WJD{%j z=L_t~w+1dU1*1V0KiZaXn%G$XnhKWMKn%xpMRZPoSi>G*P+E=Zt=`GTxd*1{u1Vhw zv338e(bwcb0fC3tHP&>;l+LfqvPuzuGS3!Eo6!TJ0#}B`cbgad#A#Z;UsNCQs{b(h z{ru#XnrwC?nNhBw4FD6}mh&&VPhDk@w~H8U-2|xuK1Yui%JYxK_>fBva7OE)Ow-_??anv}_2cS+!nG{wdD5tUh)lhB{s(i=Ab&pyIyisk1l zeFZVvL`X{0oJI&%QRj9fuF|oV0`Jv!gizlrO}Sesmc|stGCIjKQTB0I>AeyLm($f( zXdu1^Wf7KhfOCAkOhIoPw#?YAjQTaub=VZgj)KNcXr<`&g%-_a;yfgY0JC4KF{ND+ zFw}Nb9FbHmAH|!#b8}$Ui515s_Z7sH$4dbs8rUXgKvsDhhEJN58j~0O%1VAAFmXA9C){Db`n=$Fse_8DPIwZ|;aO~pq1C}vC6N6(RF(9i z@y?_}k_Z~cO6yVhTK=^JGrn)lBW|xPhaL`NviX&}&^B;AqX5d7C@AnK?9sGhaTR7C z4R@6q*OWAncnf=r9_gFmFGNwu+=UvU+gHK=n63yF$&n3J&*S^s@2K49;mOV{^?Z&aTHB}dO zWq!1=#{DUss}!f>YErP;YWo-rA3N+|c!Z=yD#uU z!KImBKw~!Pss+4&2<$5G;LMA!<*8b73cF&cs?lN88n3)c$<0{*U6T$~_gkB9p%T+} zsJR3dU7=xKQJ=-2NjzT-E`|{@{VSsmJzAUfRYm9&du;nf#NJYWSbujW|B+uvuS;AV z$!mx5{6C~qx;}QgknpDFNAVc*Ld3yxKx@!Zr1xT)O0Yd!FN!13XGz^Bi2I=b_N7rD zi)%qPVHu=O&e^woviEn=N&FI?*?zepw2{LR%l{L3&zv(lwXcH@R4U?9 zBVJR*H{iwST9so@++i6EsqOA;c_rJ&C}JT@H8wcLRwL`C9mCoGE}H+OvjI)6qWmdK zBQxJzJ+yM{b*| zjSm51`A;%?<(h)*ShmBoU&yh|4@ z6h0#eru%het%_GAR0^I8oSqR}cGcNC(~P?W zIHs7#cTBielmZhioH-&xo%PG*ebPU3j_8sWb7IC99$L#qKGf8mnJB>jcV)A8@2+MXHs zJn2!zVYhdjBy{-)oNrVoRoU%!Jur)oZA`g-&02fry<{OqsSV-VJQWIfE(d6+@ObJ- zuJyW{M?pXQ6v6RCN$yjVIgVrPthXx>a>0c6aNhhxsjQ}+R7RX*kK@-SP90Qn8LjP0 z@=PXn$g+TK15{miI88{AHp$c44QG-AN^Oogn@-6o+oP6?_k(cz{8Z$>3#Xmm??PZq zTeTTyJF4z;PcxXxZ_Ne|UO#3GE`E%|@ON4@4;r!=rv6aH#=PQ{xB758^JnkVTIQjAng{YFCP>WR|jRRV60hpRcrOl@O zVOwosyVZno*!1WpFNG4bEwxlFgIL-#s3R-%Z^)H?mhnNX%A_RW#> z6eVU@)mE6j0~pWyjEqNn7s4S&xDIt2J^w3iF;AA11b4wShqB@J+z4WCKA>$3NxP-b zp{68)O;q7rVo5d!+4ibN!RmVC;zlX`C!8Ajo$Z8*p*?z#a+&6Z=<7b$w#}rr<4vCDn9dgOczg(=WYX&ofY=++ z8cOP$;WI?!8TUcekK~)|Nd%6=52xtq~!{>$< zC&vw)Slyl`u~%`8;~!;#&Q-f`d7REX4D)2OCtqphbZwzULfE&tT2!^`m2{s3XFC47 za2l+Fxx9l@r6$smw+W%Zxu|kk~-wvHv#y z0UC!VQB6%q8YFfjD5$M;euyyu zdK+-O@ubv5agqsAxlVURG#@Z3NW#s{-QyW$h-MIsS?1Fe?^s0JEgle2e6o6?OzSt= z6xze=zU~EdsXMRjR#Vj+Jo<1$K&uzw z{2ui{dF4BNv)D4|&|t;?)9ZxxqW}>K5cj}mcWAPR@}zT390_;2r>FgXM~mw#1H8(T zXq}&>DDLsZKZC5qh1`nJzfK1(o_7`yQ)nxEz^6VE`Z)y@szszSz;J@`G|wOKB}^3- z53qzkI!b$uFZ#9E^f#WbIYzKAjE4Y4qK{jxW4ys%c`rOZ0?nNfp067mn8 z+2*kqaavtPSGV~zXiZELbn|E%Cn#zwgg?Z4WQ_A=fjmfQ8m4Y<6b|elXK&o`M;{SQ zrG)KLosATYOE~lRH1P>qEX2`T<^)<~ctBZn4+&_af(BB;b)B zFDE;evvAbUrwna1og!3^yc_lVtm5fzLmS@yO8O3&p@8|+xHbA`47e>6|+s}Ua77xW~tGLUxkuiECkO4K&C`#eotbILv zbo@pBn4e89 zqAlAhOT7HCh~wjnaTY^d00VlL5+fegtQXCx=wfmEKrMSyB#AD{xCDAyp`j%lf$}OU z8R(FF9H&^e!#|NN=F3^@iGeNL#niMO zPTeswX+F{&fT=j~c)sdJBbNxgdzP6Hm8oY8uNuZ@Z!gxV*0GGXP`tXp?WiX+O?nA4 zJq>Kq??}RR+>VY2JVdLd>diL|A&FUlYW$HsG{rTeTcp9SUJu;<8OxWk@yDQ?()^B& z&m`Yd9*;h)At5$kwy!hlS3(%1!e)LF zkz*V%Ng_LLhm%{|VMfh0`1;B`i}E58^kWXUq6sl|YOHUDmfZhCl16V?XUy_v+*SFm z)@b|9V55@G&45Q6)EQr&sv}$GJpcygYhnu#9|}oM7IdiiN-TuAh&FF6?j~+><^?`$ zHz@;N9r-Jipv$p;r*7z3R1hfn!Dn;%< zi#8`0$H(rm-yHp-uW|v?M?Crk5PRPts)X#PsO>9{=KUG*+R*r!l-*qavb>`mj&-r> zj;&Y<(DTmJ{N`~=m=kS`Kzev@Sp>R4jmytQG1-q&)qa6-z2o4G%>yCbCeJ76zC>>8 zKVdqLrA?xgB9WE(@q@;xa#cewA_R;m8SA+E%s}PZf@bl{psONDiAq%FPOqXX|FBnE zriFtw7ywE4>ES*D;ZmH+n-$3`wG>=fI#>(uwxJbFSvM@u!DfdmGB8@k_Ff3zY%IM4 z{t7nTA4)svf&*DdGoMZQsDGMe8B{m-mo$yH1m5;UkU8SG;!HJlB-8E?q5BwfP^Y;4 zy?o9VKZDF>mBlU*q?7yT5dN>iH>JVQPL1Upjc?iEJ{6x*iDk&3% zTv+4w_c23$GIXf>j`ElA@WyKLjoZ*u6RBpx>KbN1` z;p}4Fv?hXH%oE=mN8ruyi_SSA)YKnKiUA#F{ZH~}r&| z9~cA*h*$YX(fSW>Br~&ZbxA9@9CQ~dIh={03?G^}?Wk#D%2(V6?s@dM%;*RAS< z{oQm)vFsN~=B6#*e*v1BTd&4CmaS&ARMt?m75y;TPTi{36X)ElSc;ooYr>ZTrbtqm z@7F6FB)9huls|gXK|i9P-Rs|Vt67~1d?W0B<)l^umObbPeb^9C!S?gelLqc}j#v?t zbk;AS9}!=^_p34dfvJ;aNKpLVYF2mu{y{FSa&8o}yL#vv3?HUl*-MsG9Ej5bV|o{` z=ZC7TyDzYY59dxW$6!=REMtuq)z@ISK2elgZ|Fh21w2lo)~l}b;0}XIVvP8{6d2Rg zg8C*O^HLk{cLV;det@F@2kaKU@k)mZ(6xBsPC8J*%9MqnBbgq|d@B`PdCr&AZ-6#k zzD+IIMN$aN`f$is!{>1tq}VBU==qb+ zVOXavkedrFnxfme(`$F?)jg($y}x7n|AOhW@Wy z2c${BRj3_<|ImNr6epFp^En%A7Bed@+eXGsG88m2JLnQ^lE zOTKvppUhb6RfUphg$Trg=2OPfN4NATTV0gyF>UMnYfKed?bnAy$aec#j3!IF=896I z#v;TEYA@V*YWmaW%s%~t>7AUWSJjJZ*1;ua5rwk0pDwlab?-*_iN9OFYCLb}{(K4a zkBH!>(>J~Co#$86?O8WZBx{x`{vKzRj#N8Y8O^^n22_^8a!NzvKh0NXK$onFxX?no z63FJ~7JbDQpj+>k|22&JB?wq^z+wX9s{T&Gq)O4En7zQiV+#~08U9r5v*8GZ+MwOn ze-G0&3;+6V5iImdcdVvb8SgS!X$y+m(C2kLRoNu|{#h0GmOL~}09pQtb_`2;l=H6=NwgH0DGW3J>srmHRcqOvH5J;Fs;)x{X8tIA6l zwG9ZSz5O6%da!ZpZ65eiMKSQB_PyO#%9bggHLntaKa86&3x}2rAp z>N9gf>{`PAEvdx=89ZfX zky!;MH7>nwGGLwi7$3(V!e&XMz`Z+qx}f*5QDeH{%hxW zHE##=36Pf#vGY%fEEpB)(JnmnG1FKC|H3EK~W!EG*IZ7b#`V1twfEUwzCF{@5T zK5shocf|kSpnh$e*~wiyd5JBc(!1XW=h7%>F3Z~zOgrG~EimYj_z1|J6?|Y4*X7x8 z;~&zGQF03B6@UK#xJY(9qoEt!=%Y2fm(-pICcClGSKJi{i!Tpyth^iIjot~nQ}DiD zyUa6|P~!xV^LvNB-Rty-xDXymgjYB2Y9>Ct$_;>9|8Ap|{i@)``fow~?*Ze#?uZ`@ zZ-064a?ktp(`d$WT4|~53#gslpSAj##Lnwl4--a2!06v!BCc3IMbSFwUNYDkorYzQ zlYJrAT>gOGBAu<#l?j!~6$s>eCZAC}sWhSj?T1MBZl-g!Zft#-CG#9`x;_-qYKS%m zyoRc4NP7Ds5&y&15q>?L-LKIFw{KxzP?cx#KCr>zf1?lf8ou>=*oPH3oTtwmDO-62 z8**QM0MLQ6JQr{j>X}cc_%AA9~?RQiK8O{ zsbXWb-nTar)4XF(Vs**%;=0T@KI`i!Y}2neObY2-=j{U*8+$@@QE2mVev`C1JvC!K z7!mWdwb{WCuISnb*B|rFATUjQnbfIbhk-`6sFVs3ff4Ai47s0LH0&Q$WcghOAyBvc zLDSr3O6{c2QFq)-a?8U5juZ-INmZGy2uZ2INf6fugI7R{d?%(e^#{gQGWb9Om3Ur- zg7)%kxh$N7HP%R+sFJ3a08U<1+TV#3yQ&)3yI!IyYP_E{?nv3N7IzHbx~PUHL*xO| zB!f%V&usaiS7MBhXpbx@=~0rN?6FjMO{TANQ^Yg-jz4JHXfWWO$-d+jh|F5Y)V7^| zaO%1*eEy(tDwVrtN0JNZS$ckHG?2mgtA6+jj_Zj3Ie&(3wu74Dn6UcNY0?$X3?G0~ z%H!^U2m)A6?IZoK)*Z9;=iB;n?e$&hADE@Kdh8!UUVl{<7~9t zQqp^Kj`xO5g7;z^X`%B^n$D;EBCiSaekfYabiL+%m>0huvU&Tte@5TRpeOM zF;QF$^pFa^4Ls}PPXXg}V7v3-JZCb6U&#kS8gDds9>F)|74tIE2N-GM^~l89(-ysc1K2kkX1fS<(#h~UTBd`iIW;76eEwAQlKoj2?PK03d z2rglP8p?oqHA{mY2i_ZiB)YEslv;ztw@cqcs}g8PT%!^B?9Q}<+%-)e21zU734uw1 zjueNqsX*?@Oux)kT1V;x?O!_}4} zHka5EvPg5g#(q-1!Lvf^EXOv_C>?vpn4Aq4Sa7(J2cg=DPWAdEA9{YdW;%nypi zG+x2BH!W2b=Al1c0k5V16-k{i%BW|vK4Ke%a;*fMe%|o5QGJMN-(F~j5nPgEEI~-R zH)O+~eW}HqnEbhX7AOeZsavcY4&f+iP^`bbiYPIr=WiRed zAB&^XSjHJzSoKQKaL!E&$Qu0>O-b&{?8N5v;{iLw$_~Z4tCNaXre7XvOQJDYOccRo zF8@9`v^O*yBn3<6dQL^77U>ba0&4M z`c-q2I8>J=xS8EHaLIj8*F0BJz1CT-x{t)vkIX8u-hn00*ZL=0T*f0aExA|cJnLc% z%SwK5j_sKb=F=2^B8<;NI??ZGN`tL#)y~@;`*K{4A>@aea|^rXY?H*~sKMm-3^OnH z6hv|ur5t0^#un$SCh01BJbgXfiYeQYLVE(NQ50_^<%W=`QFXy}Xa^T|{NxefgAF4O_k16Jz8f0+w zgUAUk5Njuj=vGxZj~m^KmXCBjwsBrsOD4>o88}ol=;r+;N#(>3NZS$|!dOWABf3RQhP>u6V0vYR(7B%U7wQb*K`sQDf1nuYfJB>x_>UODB#~!lg z*SdM*@Clz}K5O(3$y3gjG9GGt#ZN9U`SO|@zvjklYs*Nc@Y+gDl*c}82bvz(wR3;U zTfGJvk%=1TJ6F8lE)sOOAJMnL+2zUjI4idO9P#@l0W`0-m>KYYDTrU+XSCGGNs1*f zmM-emOPA!I)UQ517s>+Yze&lY+N7PJ=Sp;bJmYOyz-ZAGHHnM0wuOi3gd+8E3M_dK zQd7K*TI-%;qhFAnU6PsTTiIhkA!Q#+g$y{p4}Sho=s#xo&4MN#X68s{-OEPvcxAip z)1N-gNn#!XNG;ygUp69_!839s7)8NdA(f z3kjy^*8?@qv@FQ0p%_|kAAYWj>9glVzbz=UUr@)S zW8kU(jdHkU6-T0uIi0)xjZa4oh@2rl*`OgQ3mpzVgPyvG_tldf9*RV^Gq@fm_4oJ- zFu)_#e|2=w%6DVW5N|sq*Doa3P`m?g?yReT9md1gq#*TM^q>jPyN_#yb1HQD9u_m~Ko#?m3d&BHk9Y@FDS~KogBx6 z5tyKebMZK3^zE{pfbMAqf!|-C#0K=w$>;IR-E5EK9~wIyZBj%3akBdFdZ(i8qPI3m z&`$|yR^aChGTW+h>BxrDtAGar5Fj1DcX59qo{!Bv&r~th?}(DFwwEc&2tkv!%~L%fBA_H^FGEG>2kE`;$O!8Z{Z5PvcrIR z0X_X+j4%e&#iv8KtmbX9#V%s6h_^nV-w@cfH;;s+ECX?FuL7lYn@+2Ltik7Wzm0wN zpC;9tc2#jD@p~FbOIo?@5C*qx#NohU4w!rw6y}moSjW)LoBC1)+`Ljf&x1~bi|stz z!5@Ho6K0h0m7FARVtW~0)8RZ%r8g|W3HZG+U7U&FDmy}inHU)OC_|$&&k`EzW7glK z@HaHhqFwV*2LF{i_Fg^od60UVoj?}7pWUCL75r{;_nk&Ma?;Yfu@(Fn|3#qotKIh? z#qE2{YFzw4LRI+%RQ|VWvb)s3)VGn-bENE5mkbv3N#jHl zQ&}vW=Cr_7e#Q=APkZFq0+@I(dym{%T^{;i5ZF4lDe(q+Pa?}e1MFpF??Se|fxmd| zy83112^G7zcQ5}`PqU!bf*#Hv=cn2s>l=oAO6BxTl~9=?TXhawYa@L2aekVtN6x_Z zV@RRljzN%(nJBXiv>S>Mwz0lT-1hK%8|hGInO+k#IcP8sCm&dXB<7fuYzZ4Iwzfma z3=_;eaR|?~id@8>{v{+n0O_;`VDN<;X^mUX1lj3u=;f&7Ldjz2a+Hl)Ltn8ypJGsq z8Xkl}_OBO^)A^stPc(Sd^bZ6GatKC+t&zBA;{xn59!3UbblWU{Fz8!ZK*+WijJ{;@ zbZ`^Sk1`ks`XR-OpJnf__^f}`-yDJe)4gwq#<`?ajC|w91e@rn;6Bih*Z+ah)XjDe zmXz`h`?>fzF!*E5Uvl=ff8o#2T9f z0(tEF-5vV~o?MugiJ;`wW(IldVBh8!5DuZ#Tp)8z|C9WMmY^yB!ymlb@*du0TjC;& zuXr`9MjXoUV)P&UU2)Q)E3oKLqdzgxR!*5P#I$p#oo^%Qz-%7OvSWJ*F1!E3-?jcL zbuOXj7=CWEG_xOWgT;$5JjbjkM~*>(BMapRz$zA&>97j11kudy(TTSE$e<+z) zP57j5-kv=gwV%8J4UF})^A7Edo17#60o6?ZByM<*Mboi%tkqK7g6s82do_f=j6lc% zg7quS^W{}Y?VDx`Y$F3!d!!3~AGxPNi#*DZURLl43ksCD2F4Q@?R}~bhrlhzte@axpH6`GDlmPg4;`*$Ewq+7qDy`XCMtdmCYx$*C%YE# zGqtY2HdlyQ`#XKmmxrNHIPNGqXK6{cjy_9}-AOjZNJDcFRa8jk#z@+D1 z!y})ByRy@25|!Z<%q3R8UoiNoSGogex8Jh;H>d-es zmwZL?dLsa(0hi`#qOxDkH<4J9IM5;@NO8 z@%wBq*MrOI=bXj;U>ydNjAAN-A9<<6zg$pNDiCUp0`zQN69zfQ*m)pvc+IX8rg2?d zy-V(arV@%tm#^+=palgCVg52trw~&1TN3*NL|H3$W4W?7u&|?5QE;dRaK!5`?&3et z;MpDIfL*a6Jc}4xc}H~Nt|}zWIQ=2(DzrH0NZ`)yfc37Jg*FPKA|Pb1%Z>qu5l!oG zN)K4u2*wfo4YcFKgErRf=04B4wrqC>o2SfDM~51P?F0rW>YiCQhq8azm650jz9FFL&Idpt$@su4WbctL*!uUA>`|H?nI>H8*F`+c2^BE@hg1*wFiuLy@I^QclU*rT;IwHdm`IL zZqjuO^-VC#Q5rBG4gP(|0k!xYy`?8X4QI|~Z!hkqe95@}n9fQp-fU+uNB1qS79^E< z@3{B!MoiewzO;5A`E3pdX|9EaarV-|jYM*0*8IYtJq9w(FVj==ki*N1@o(iE)4cbS z*h6FVlF{^U3L6Z|Csei701gkHHwTECjR)%ESx}-dId`WGi5ALbB-^-L{c3Eluz+Nm zL9N}89$^TF=_Cyz?vCqYloT;b8#ZNZC(K<%!Nt9+>tOOBUu0dsX>juiAD*%7wb{wS z)2vXi16_CyHU`I5~m)&>Yc2FWV9qa}JaXQD9Zae~$2(e{w$0@M+gl>7!)p zWH0-Q>YBa6jG5aH&+MCckQ>XvIe_~kJcWPzNXsp#ydJ*AI=+W94(JlIF+e)V+xs73!Z%ttwHd zP29LenXOmz8fC>l+$mrVL$P%ggirIQ(6=xNDeGlvPgC%W&upX9Oq0x3=%8lqeNzB4 zUtNpZU=Ze;cC4Vj^f*45PQ z$3TBzPNSENACZEaf{EfL9j;w7*Zk^7p;R$9>kD4hYfH8PX)xyZQZ*-yOtG0azi-QR znUe!kj=_@!^ZAJCcy_4IV4fCKmn1N|F_<( z7l8@8lRNWw+iQeo))Yq0X4B~m@>bXlLqlgkjYyE;JK;WX=;ezXpdSl3C$xTq`;0CmR z%#MNfy>D5Fo8cT(_qBBw-n;%>u8S2TTqy?tAlyyC%NVu(BMd`FI zzgIv>{Pkj|m|@!f@mZGYZ_x3Sz8a$L>$25N zrfjiy@E*PV$@X>B*6sQ0mDVI`iT!DEFzEMURpkILh*Ffjqc_+H7Uw5nQ4}0ta!+?mwm7p$3iMB0A^oZLq-_H$TbIk8&IZTRw z`kf>vlK3M&=0XtlS?o`%Dk_@82!ue^VX)#LA5J0gvW{YekWyrL&j><4yDZek*nTtH zw^t1p;+{qM+Xs3S zZn8(v5g`stQe7&2+_<;=K+TRsh#>R4to2qGU*7y)C6o%%mhfT}EtsTxm~I%J8h)TI z?J3BlJSX^Fh3mw>|N%sE}peh8LON^n|)g1U=*2^Ni`IxBOu#J z87v8fpis4Ihz<<+!Z21#yjt)Xitg-KaT;9=CwOV}@rfAolgrh?wsR;LGr%A$)0DgT zJlNVV<~~pB8u@_y!Gz`Ul(YE!5$b04>E|F_FiEYTd{i$T_Xg1?gVIsa8piz*RF8sN zv|xod9Ul;kuq%K`x`(OrW%Pim65rnU94K3!&0j?CKdnve)ne3t5^1X!5RIk>I%iw$ zqmL6S-q(Fl*jx!eX-Dl6eqSkImfj>LOO%fmBK()q^xtUueK)7gGT0UL!CXZt39KDw zY~@N}X!~N8W2Fdk{Qq`Rb~8|m&)x*McRknZm8ZUJc!kS-CB?vl9g-kzT@zVVHFz2{#!`<>66 z&zftkS&I^akI~s5IvOn+5Gexb={4lk{4$YUgPibkp5QZ_*Lo;Txe|pA5r!7N_-{a# z^D$|zi*V{*?o)`pjz1hdZS8+?yODC~E2Zwq#^50gZh`tkCY(FJf=CV(>1%rbnjCQ- zO`c!fsWRuj0#U5PUQ5<*TYy5#{UPj*uiN9arDe6UbWBIYP|zXsR(munI?jG?U{v$~ zGLcIVm{fGIjl>ZT)d+bg(e3hcP*h1^qoVK3d9s@OfbVt#F3yEo^EYoN%hQ{AU>u|2 z>4-6t#v!CbUFZbR6kN<7t+@@AXRG2V`^jsu=}*Q{T;G#8ci~2mmEp#RiR)*j$#MY~ zbJ0U?mreVhBF~$e#(z@|v>Ug#L9b5=(d0N+tkn4+|D#Ote6R9lxDIPNpUdaZSHeFH zz^YPzF|>z0z0vG6)G`eop87{kJeKrA&crq-q8G#wynX}$9}UDrhae>L`~wWKyMK`Y zYfe5C{xed!;h;i9vnu{svx#xTivXqq;(`9NpD9!-`S^h4623G`Tu~8wu5417sMnF~ zZOLN~Fv4M|wy+7QCzcu%fGih~UC7U}^|Z%BP@3&?jAWHS=lHR*SF%*hbkVBS}v+qJGngWfJh%=Ekun6sZ9vY;S!79G>*?UX!gliS@Iicj(dNgb_8E* zTd8jbDI^Ro@)OO8RoExvd^>RRh*C0vvJHwzceJxum2VU}Hl3Mv$5ccdD5b2*#sdxxhuXlzAX% zI)`K_T%fmaIN)qQb2Q0%#HOS&d!Fd&%D5BcMjhFmTkYedcrVG;Muf659kXAo7tuiU z!tl^b6F;|JQJFkp;q!;V^&#~x;C>-_Q(lX_J%Bs(k8Jw?lIg(@c$U@>!&W<$>p+RS z4$W)iDjRlGR-RFD;pcp|QVu^uBUdC8=m)7O3!zY_+~;~haylAK zIKgtTi_RgE-Tp*Q^#|H@p8IAFNTmNf_;}4n$BCuFHi53o(3n^1`$4l75>)J@N0SB+ z(0yg1sK6^MXFR{IUS1jyI$o)Yvi1hb1gTQNgP2&j9ap&@ru*Ds_{nGgVQE};{z>|WP(`x!x@DqTQbsHmGUn3iV+Qy+xumOVIfOY z!MCvS8*#~460d9^5P>!^?9`|;wa3N0x?Eb6@%ByCG)G?5OamJ_^1_}t1R&DCoKb{w z=N<9mJj?hx+N~Jg#X>p$9*#Mt&LuKAu-MH?1{xM0)D&fTgtnk)q|kT!%HbQ@bo8&^GL1h|~;%e}$Jd){-ZmY*Z>D>mz=zPh2$JvY^xs!AoDg3WSy z9bvvxL$W^a__Bt|xEO8yPraXjihS2Y0X+pFhiBaSgYVRn)L%uK(;>E?TL(;kVLoG^ zAiM%Zf^hn@B|9guJ&C4I^@6xlz)q`)dU$V_k=8+Rx(}MV9OMW#eW?ttAn*$BCh)b`6Xx&2-HB%GB0c9D5GAkx3AInqViMu}5y7)_Nk ziw)$d-S}x6a={cNOeyNBH@tOTzaRO5?*Xn31M8SDqBhfgKfZ=|jOO+F!%e=NE>~@$ zX6XYYlt3@>0fqV$d$YmkSf4; zEwu07lO;aYLucFjZNkq<%D>lIOn{uCRm+0)Pe$lc$NW}d;0C|x7; zCb}isVkIfPkU+JM^XmR0Vc4>Bgo+GQBMjcMJ@GWmeRHzd?fX3twels6YvYHx1uxAF z_+tY>!he(r^iA-8^yYL6$M>bG0TpXoK)ft@N<8pkQ3*Qs)KB;}D(CbbN3Ar@gy+-pVhMhwT-2 z#tl@hC4=@8Va0DWg632n$c@@0=M~kIJbq5UD!%xX-GE^VKz-A(F|)n>6;>g{8-rXWJEV#2 zN;r2(Q|IS7)hU@2FD=LtP)k{VVyM8SK1cw?*igYGv;0!AAbB}4#%^wgJJ4-Q1FRKL zG)x`E#^G$4swv>Ps?Fqi@n&9UhWrE3AQflhmZ48ArTk4Pvs&J&`Z%5{PeiBYsK%ba zqD!~xxPX4=kB1{_i~WVsIC7;y{>dA=A;K`(-TNI(zJ+LtW#;$YieK=+{X(T*hSfk; zU{kV{Sql$pg=@LXsaoxYSI>c(Q^(VQl9MOB;&jb#L?N=~WVg zAE(rJv|dUuJd9be^x$=}oi^wwwz;oM8PWDkcfmaIg-9V#(K<%PA7mKFlpJ(Agcj&!KFE$TA)PVxa->muRP`J@Z!W9L z2kVI*$bPUKyeHUJ!}5nQ;_U8-OGD6y9)#wcIxbys*r)(nZQ$`6n8(v?(T0rBKHNy? z6C~;tjQg**C7d&vd+A?*@=$5YQ2zRK-zH2E_aOFFUK>AM&T>O+_xRS38MmL|!yom3 zT8}x+ZRs)yVKM6mW^SxRIKmr!bHhND*|%W)AnWy8x0)f8ga&f6k#u4Bt9pb7xIRt+ zco$w$saYPmq#okWT6!b9 zTbY{-Mf%j?EgI2jd~nqV`5PWttnVb^!gkAqxcc=<74-4C)EB@xoZ4cJDR5jlzk*Zn z8dk59b9S2mS1yQo1y#=?v$-l+9Sl#V4_sUUCa1`{E+&ex%EvH^cauxs_)n`mUxo*j ztT_L%A3#nW-?(a&D9h``Uiu`-SEQTEyHHdBmLUEO#et`Yg` zV(XB&#(8(LWuKCUO{6{+ z@Kn+MY#D&?8A`5Cr*9IVUZ8&18uJpa`*F^S6&SPerza3ZPJa{jqG;{-b1mduB}~D= z@{==%i$~-iv$912=t1iS!mKH>WCraXgH#YE{?b z2eExzAJM`9>sbwU9-cb~vlD+~|61!%s&Io^th>d@ycPf>t*8F&mpB++|4zjJY*RNwm7!bHt{vOO;5Ii*LtSl~1_CBK)OM+Yizou#DlhQ}?4mKqI@?EX$mInMQ9Cx!Z#@ z+uRB9gTNVT!N2g9K$O$sp~3B!|?p($3!#9uNtr^J+jl zj`zM_sU=Qa+eA<`-GKUx`Tq3-Jd+8w_Zz7e>V2;8vO2RBU5EHV#-OKXoxGnl-iexU&N-; zmZu(~j?OBQd4-~6Uc5>DZKD|_bGaV#sBzb9k3>SDDh&5{u zs!0d0O)k4zxn8}OK@MkwJQKw4r+Ec1fd_X${|bgDH|duc{);!D^4IyhB!dZ7*gc=F z7Ic@|?A{=cp&O1zrLhzpPm^I#;UII>HU;QSA&k~*+j<48k=SC)nq>Ck;*x-;o+0Bn zVqt`)r=*$co(U4?PQo}5#i;s)cdpqZ5h=li0@ho*uC$Z2)OU%J)z%Dx{#4=?F}wWo z&ZCf_H3=>wnPnIZ?@w(L7@pST1QMd-6dZp!72UoU$wm#G;;+DcJ9v=%V0a(S;tc93 z^-kOdCcj}=wrteIrF?q?5AoMn{tQ9(K*v$zg!8m^%Uf}8&zo}i5ICHmDcL8z77x}# zf^5Wd_FV}fH|S*fb?|Kv_bMa@Dw&t0xB4EPsl?u4T=j_Qpm+Nu1%~&i zEblJXh#;K{F^Eo@Wi~b7?T#pdf_mV)?1$03_fpTJZB$EIDiRm6OM_Q+S+Wb^9>KFQ zG6_@4cB`x2H$6O78>aWsFvu9lb){WCZbJLj3A^uLzG~2svy`+_yUppZ0T` zn}0=G*FmAA?UI6OzsrU##1T{DRhmkE@&w-c4SXr%)YBZE^xYP8p7y%?kMvmJiUwF8AbmtY7e8 zR~K;#Or9V(D!Y8a{se{>wSWW`A6R?Fdy>n%@3N7mtMiyxF3VaXcE`>GjgG;IVK6xx zvA%ywidAqkIS|3`LWs9`ZOo?h2%brzTsGvnku8g&?BEKKX~br-bw}K=D)w}?#Zm~? zB=rM4Xwhij>~mML6KU-N$aP^*4U7z3$3Sc;^^!5-xL};k7e>CENq5%MaL0-NKiA$>&*A-+z1zUK z;3&FRU7?l%Yn5o=@vn#bULl*p?|QL*%xzt|ijtl1u+-I|$1^F)(c(>UWz$aX%<|1# z)63>>)t=?1oE-ezw~Kp1?2=FS01wTpPV;S1*xOA4nlDie%^GEPlH??GtOvZ=rAflC z-T|GE_E+u-+@pzak{MR7@Ukb{<}0XQ&)(yOHQ+;?&4scQ0@kxPOrXn^D}SFR#SzKg z{U(m{^u}1X!6e99v3!xfVdhVD0Qte+>kh=yn(Z4R9k+!^G(F2ReV3|aD>pktzLPtT8u8r??lGNFD`F3`+i_^zc1Dt9V92ELeo)!c`}A<} z$$Fk8_qbt~z43@os%##oQt1m(VhOx}^>#@hzp`Aw3RpmtJ?qPbFQ!W#rwm69h@dS- zcV+peumrZA_oE|qe5Apes?C||(#_5pqfFX;WFuh^ApSNJ&>#lhI?PTSx&v=JkY;-P zzB|RO?lakCe|;e5C4T9bt_b1mk%IQyXPK~}chXK4Rjkb(wZ9`impI!#ikpxzbB)>O zA_ry0E5o%Ta5?kCo6(iw={&C#L4nYFGpr5V7{t_%q-OIzzonf%8yz_0wE4{#HKQBi zYxZORV#;pY1AymvA+ekA`TVxlZ8LQkU%@Z^DUUg~GtTVV{%_0uG-3fTy#JN80j_1y zyT_c!FYV-OxyR}4i*0Axx-h9grh}nsH3+v#VfMOsO}gk z#b|5>B{a~l+Tkxex!&hxqZo}VMqlU?M&WT4Dr#j`AK*cSNB`apSx7r)^`J)MxTpUZ zk#ykRBWcDLVsJ6#B_D_W08bZfgvKDu1Vp2c!UTL@4%X>1A5?LC98yH)TnJPvfk*`odq+`7q^MKgSZumbd|H z+{O7}22XFLB>xYL+korWh-AV(1S6yngTr?VtbA2iODo%jI*YOUqa zno&D;r3x>II=zG+1;yE9=35Pd2N^r|Z1Fita|vj>q7`aMa62yU$Bg;z(PNt0VMF>qtRSs6_TDN5(} zkfJpMt>{ULZ%0rRUlZuQDV>wXJuD4<_2fd+DnI>)3v!f)C+Olq3@zl68udfV+>kbC zxQ+2n&7)Evy6i4l%$Byz6p^V2tnsVY5BrDJUX`rf?2t3$i_7?p<>co_%LYKpfvm_G z_+|-cJOZOVm-T?dpR7Zelw#0xJt(|?_Ce#XpEB+6^QCRB1mvhi7G|`54n}Y7hlXtp zwyJUY`Q<6NKkg5quy#v-Q|ma!(%7$0y|L?E5dW$+J<3;Z;)&Y_c|$Hn0+bfdhSThx z6EnW{u|DW6A^T2X$1w1kwisos=2!Ai2EGrV@my4-TRald--$O8`BHe>vwf#_WeURC5|&=q@#uU9G3k#;rlXuJ z^)I3Gc%a#k=lGwqil*}vYj~`Oz3yDcU~cy71%k~UB7)J4bIUfrdC+V7stiNWx3h`6 zp+4uv4*Nqz?ehJjNKlS{j$>e_*w>TPW67*UY(h1!ZW$}s&`Td<8&txu@%Ji^W{P=+ zB@ua%xbDBaTU*YS8$2QSpm7rqR~%~WMHAFtZ6~*9%R;mxi?g#ce#kw=>fe&oNx%!` z4?G-e#E5Mxc#}$xkS5}>&35#s0-rkqosz&2LvsmCSc0zOcc~k*PDX z*UuB>OZ)~hIba9inh3=k0YXhKpzwZ0HqsVlaFH++ie}dMP(IV=q!BAZe)|ev0f`Kx z@fg|q6MYt*8b#lX%%>7tpXl%^sI!Id#dp-1^@Ip%z?b+xA_<&mK(In39f~XWYzZ8P z#(bFJxwqeb%8zEgJZj?lTMu}Pw{k;pzY~VZBNLhfhODasZ|R%y5Vhg~TtkKnZ2{uQ zKZ*o({3!Cn z$a<lFS$FDo&9sKD%_-OpH0g=gQN0H;p6Z&NVdJM0{G3KP>SXz(`}J>2h|GiAT0>0cY>;-o`c1sLY!YaQ69J#0jN-DA{G&)en-~u1!nYA_ z;c2N)Qh&-k!7#c%w=L;()gY1o>4abUAkqiazS>_xU9aSW8LIdogk250n?AdrA-uVI z`>uI7=3c(ku7L z$cdKZaSy!(3$%7O*nw4x-wnTuX<7p+A4D=hU)R&yk}s*(D-{gBSg+j2;B5O+Zn8dS z*&|D|as&%Jd?Wld4I#Bmx37bDlw-WB?Du&VheWlce2{bw$5*Hx(|;5RN(TQ2=(Au? zP~>8pvmAn?^&`QBaEK=zr#8*5tjH@&>tr|EdF;iLvP4z6u|=5;25X zZX_ej_6n+-iYA3+{0)#L#su zK`E>udEwC$a-{cd!)x6F8gsh%&oTyijO5_(1eBMdsc51>t^kU({jVS6k0%+w?qQ$! zH?Zt23wkaDIZEAC}17Y$kp| z(jF+tg1GliC-9J5fJF(Po4Bpn$3@{c=x6@Jw~3Y#fR4(O;flk*p4VKm3Qx_I9poAq3*7If)W-*d@*nKCe%e06TetF?*|(ZQgSD$t|1(bI zA{XP;E5@>Ll@N8+NA`>MNkKWuEWmIGm}S2;m-9E`jc|)vp(#EMWU7Irn$ZUylw2x- z`OzZVi36$jRl!A6#cPyeJ2cHQD`WdY z4eYneF5tmgr_p6FK8V#b@ZkVpAKyD!S5N#66NcaGKGEs9X70tLw1FGWQz-w#fBT-o zK1>0!my7R(=Aoqo;XD0s$w&4}q)Pu??3~@3`}|(=o82&HW~{9U=t4TDpsP16IBkQk$pQX0&gK&8qEFn&->nR3P)?w(sVaFJv*+@vlv zn4we`c!UN;+Ua#?Q~}4a%={BKc&X=Ih5qSZwt}-BO;5>IbUzKx1z@g7H1Nzc}DU zr>}&)mWk#N8D=|S72j=E8ws$UyMd7ffAO`d z5YZRj@aCj4@t^3=p-DIElE0IYUX1GX#wKmjOssvx&*D~VJ~5UM`RKq4?KaIQjb#ex zAW;gKW_y_0{-*wW1Ryq&@HNb&6I(^ptjW0jcf$!QiQ8m^Fw94vr~-PvRzFa>@d`UG z7UPLUbwVnRJvPBafnD-iJu=7g%u^R}>rl)dW-z{8(!c)YY>1EI=7R*grBP4Lflw^s z(I++eKzG4bkX&H@!Yc7IdY+J@vTP5IF372Y6_Y!#^%1^?%R0Qt{Zv7VXY%G&%|l1q zW^=7yt?AokayTn$g=FW!uM-LGRa^_alwyJPJS&J{miDye$ zGaxmE{+VE#6nvl9%^?4VK8cN|S!{b*k_e6MCe+9=Cb1Ty$G8<3!0l8pLN$-v>Rz8a zkoB|Jls&_hp=mP85tTrL{$=m03_Qp;FzkkXse5vJ6eCG~o38%fYU?y&q1{&q3FphM zxopB-3)(8nbe?rL3tEPoF&}e6GPvN^sRUAedVciY;9?fT#RX(v$VE;tJh1(YvuhTV z-^QH||AK?vzD;a)oKTs2gwHtTfHqv1y@KlVvY~L;^lY(07#w7I*cT38<|9W}VbFA$ z|M{1cm%CGjizAo~U1lsTlHm~0Dspw9aR|Oe$i!#I2Tnk{zK0GvGxP5kKhmX{W-t*I zdX^O6i4{2;^P%$codQn;RkS-}#O`z)7hZD~zl)S==)ns(y1^g< z&%A?LDf;VQDy;?d(IC^YHD2$g+NhzMUxZ*#1QI+I{k#6iNNkC=+i4I8y2Pux!a^ugXMlxUDDo-si2Cul{)vB&IZN!|Djw%o>&iwfk*d4vC95UT2*@Z z6!wZWt%)kWQWow}gVoS;3J6R)`ZKNoPeO`i3>DMo;LqV#I1waC<*3RNGUwYUgUyB)BfgC{lg ztVMaFytc#LXa=ZTCi!kI!H*uUuh`1@LPtc%mR9inW#SwQo2kx7Xu z@W9_dAf6W`$UtCzO7vmt#m;6A!Jpe`#1c%KY9TKYJ;EpVmdPY6`5L=0W61b<7nL4k z;Of0FE05xhP3*Xpo$?eY2Kb+U$r67iySdVpfr%}XeWiY2QPfu(De7~XKqZE}@wNjE zSbv=L_R{?hfK&Mw;U1khFU|15G)Dk3}EC2eXQ^9w;wDPN<^1X2SY@c48|g0bgc&uo4* z54Qil70R5`4~{39I~3)pWEE%y@cG*kiB*O3@mtKVBd3Y9+UBayJH4zpchBE=LXG*1 z=TFA}=D*f6K>Gh{G0{=kLotz5O&L8SxhjOg*Jn&_vNFpJ$hnVuZppAJZqBna@`Y0v z`Eh9Z6yVG8_5wbY5;(G3psPuU>a!>mVY-SKe6$o1muHzGeh z2wA_UIRdJbHL)oa8&wG_x&yuGjb_sN}%*gLuUV(ktk8U=i$s(j+M+Aj&N?7;rMXO0FaZ zA7~4o0xGls^KDm<6@*$Ger@eco$HpJxb32mOVKL}QiZZMpYK!7s7>FT^8ADFy}<^>Xu-GM;_WXD-9 zgYO9B6v70alg$5`$NFa3LB({_bCpvB_)oM7SzEg5Nd-Nv90X*tQN${9SG*>bA8m64 zNGwgJLUdq!V!IC=(V02T0Xft^dno)$?d4aOEoCn)w1P@L3HMF{pATyECTd12DoF!>7N-r-F^RL%|ZCE(_Un0FmunWo_f~ zV@S>FQ^p-)0H3iV@p{Mf(VOq|)-o%-$?c&SeJ_hF(OvuB%O(h^T3Uhe9X3jWcmv|6 z60t@LJ`dAKto;GQCzu+=j#&BeKn*DyFn>YzQ>V5b8_r`W-8g)6ogpPz1ix;S@yLG5 zBP_B%)zY0)8u+zBB3>}YzHeQjte)M^he(4=8^Nu7$i_rPN#)T-4bp-eKJDXoC_c$GD~Qv3HS;Gw(2U3SKYU(4y$J7K(R9o=14UMG zi$yD~E1ZGf_P_~W@cMH;q!g=c;#rE}yu3Nb=HYC-AxwOP5B5nzGKtG(bz7TJ8n>hA|gj#~lu8Kw%+W9-6 zfyJtPf)QfllDa-aSCQTJ=Uw&~_k8-DS4Cnry102Q^lJbg=eJ`We(L*VHmxQEy=gRo z-#)0QWCO67Y*xwkcssa6VEdi9aDefF?1R}bFQ9)p?*o}fmJrSK!W2gn3RFvq$ooGH z{9iL9pnn934rx<}#y2&nevyPdCrf?kCxrFW;%XMEJExtVAVZ=#^vffBD@{`Ewqa3* z0jjkn15r#7DdHOzu|ZeG9o79LFNr3RIKUEC|rZPM#>^M&^uZfB9IuRb!(ze9X?0_Vje)PB-y9y08y=&UZ zQr0=#?=OgqX{881CgLRBb9uw%Hibw;L5ld#zl@CtbF8mChsLKAO|M!EY~by}#)Ymy zA%=xdB;5Z(69GK8iZen}Qz9Z7&?F)I!UbZf9l^r$O|c@auVQRxgNslcU_Vil7f>9d zMdYfk0^O@BHaRqbJwq+lrg(vlmp0Z(sbYWDA9&;rc>R@IrE>*Wf5g!@Pw;z*jpl9B z$kr3cKg$Y|3yfbAk}5!F=V%dfuv%RCG{aiJvO($*Ke6O0kYyRahjpdhPS0g}4M3%~ ztMaZ#R>!>|dV5-NX$G?2pSY)Do7fQtH_8mo&NWKQP_fvBM!=`4s8f-hDSiryuSspd zqcwfpIZ`%b0@z}BcoHzmvWjOfs7q_&p&bm5<6ps$0>!ldzu^h+q=t?qrnfw$&pQMEh!GETghUCu9(KLEOs~{1^5*$E1xwXFHftlG=-gLcCN+N$#tO(`j!9ir6#Y$UJPtWFe~9n zGN$^qG5NWsrvrhG25EZus z-_8U0G+E|mjs7S<}}`swgaqpQU!R{ZEr0OPv@kHP!zcp~q~+e@Eht82|_??Yyd3LHNl=7F`+nxfZVogs;|Nn-M+%xB!aw21zT4=UFzj{D3@x0+xk zw=mbWpI0efP`QqDlQZF^*vw3x#Y1N8ImDrLOb+j40xIeIR=O1kW2`JH)W+7?`ESi zF43krrdPo3sCZnulD5c^;yRtbp)v>_uiuI_gvnw=Q&WrBx>+{Wgy~fZT>C}qVT*po zk3_Vek5rz|#Law$-3?3fwC#OiqJQ@fJoUATrJS%G2EPaq)hwWKQ(!E7h2#|W?$?@Y zGJk(6kA)+a2mT})?i)dSElkH6pkE6rAV-dKx8n6r*mmRQ(S*vUK;Lys9nMeR%B7zx z6g06(V3psX!+};Rc*Q?QBHuTpv{RqC(q2L#XBO9J<_0p>8G5+?i}@vqbl|L7*;~q$ zMzQYE_xqwFww*qxoFvG+75-U}`ez%F%Ixnf_Zf0#a{}u5Tym|Jmh-y_L!i(6@d%Pi zd`J^-O@-63U(yXY^ezVGgkkBAns&O;$}bf}2GRku1p-9q8M_x0M+!N;5Tgw52!bpi z*zpTu>y(#F2|tS?0PxoGv7k_z5jcJW6(ag?ruvnUqI=8Ueb6C&zcD`dMg}}^8HvLN z7uurh1CtsOp-5s*m#WBF3?*B^FIQj+kVROZaHMm8o! z<%U#iz5AhP6DF}!4OCUjJD*&vu&&X%pC+O+RI6LO8oeJ}ipz&h`;M(oC7zNG$W(J8 ziw?zVchYKlJSUwGvmaNW#_}?8_pbHnZt+J=T|cnODbm1|Ct#HarG{6uuK8XP6Ec}> znJe0ir-5o981BFS@0eR(#m{C?KhyQ0!Wd_vFif9|_xAIH%3C2*3DVy8UgR5ZQV%JH z$Y|MrPh1-*;=g^8P+0(L1Kx`NsQg{9TIuG{>!U9(a)kFdI+#MH&&>`zB`8y3MK**m zx0ry=pQ7SaMdK{p_SCePZoLj@c^TZ7(2`fTbRRRgl!@z(K^oGJ|S-Io^qZjFh!^iukF!Y#w-z{^`vAt4Y88Otmga|=<4S8{hHp0@g(rwz_AL&KErjVc6`VB!^8lz|!-icW+ zfK@J>3Ht0GmY)O*>`|L454vVG((w`(dR}(Do&Z?>;wAVm0LK~gUM_VpYWAtMck}6< zi4xx$_B(|Kl`|M91Z;ozkPfk({zww5qN*)-&O!S%3}Lc{VUB6-Lkj2u{YT|wEY+sy zR***oHo_tm;+>8t4kl3jxK5(uGY$;UY^bGBq|bDYVantTD|Y#fGX-hBIM2$CB$Rsc zb6o-?MR!~R%^L~7!6<~MT2&H2e2KEHaQQ93;ZuTDcJ{Xt-3A16sk=lFya?=<^zQFf zoZ|OCR{BVj1fT3MFvAyYqOG-_aSIR)WrFIZ{$u&R?K|bt3tUMuhZVkH4Bi6jvqO); z0Pe%OfcKS7EJMUV-;MRx}Mp6U1KsJQfAl0`5vK2z{{FeS&k zqtx07KgsHslKeDqP{E;&ML;SD9h%DzbB?{Hb9TbU>mRRS~;RiMB#KF zkOxK^nh#uEL@xB(T3=D?xZfot^2}<-BKA9rb%pNaG?{BYEFbI(UK{N>d@`PNvAE~@n2j%^B#e0Hm2S-JoK7~2XOb(J+ng+iu8K)CyY-l%NJOpc(k;#1h zs$RWpq+q7_n$Y2V;;Z7$C;oUm8-1es6oAUz#Bg|V>3Zt)y3DLSPn~>svs(eT3EL~O@2^0=6XTnJ^#q~ z0xsUy_Fr?0CWgSfl8HCm3Z6ccWNzHjP^4m{N;+!!8NFWvYwCK&a(lBan7{LoCVf4P zW%lBPHe3R+fFv9(z@PICw;^4tMQq*J2mBs>|?4vN$S>z0vHtn3+;A-unBLFbc*4=6t^MbCV<9TYycDb#>^ zNJi4~Dwmk)K719Ei0_Z7TiZ^Z&L)7f$&+c`xI1z8Vglid`pgH#@M~Ju1)Vy40excT zqS{|*Ktk@RCJOt;wUXJRv^{p2-m8sm>l-7BQu3LW6V0)cgBuN^Ejq;$S9F%=FlE31 zPq92%0oh_HqBSSG=k;mZ`@AozSt22fbMy%c#!)&!Igo!535xE~9Sw8GRTO4AZfxJ9 z#f&-_^`N6`#y_Y8@(uV%1Bx=B^>EG|KKW=H+-~jZm2tdjAe%+?pz>~erv8sF<&#m- zY|Y&}m;wnaMF?i-M49B0hVY!&BaR-iJafZl3x}>&n%IOL`h3DKOFrUSDf?(unjWpw z{MJTg3+SMC)Yj9Z7ZDs4E$T;lXTCKrXqkFzf?_wJGeIe^MhhA%_pG_AhXucq&CVox z){Q_vRm_JGGtjB4{)RSjn3{SrnD`GFBYc`71^?78I-iH zSOTiYHmUg(z0(BOIGv?EsFKa z??5vBan=|DCS?!v?b?k=s zb@jmQ|KoS?+)ZF*{l8Y9Kx;Js4=}4G{o<}AaVc^_g0Io&5ox ztC`dYfwV*?VaL(%jZLKq&gGD|R!2MH$~4k<(X2>p;B5znI}z-a7m%GUsFJkiPO8%h zFiI4M!%w^siC&TO%mc|OsM9`N#w_}ws1&DS4MZ=|;Bf8NY-MY(TU3_atzVpp0zf1% zEY^m2qNK=1wM}XGM9OaZDqhsI8)3%fZHMl3@s71ACLq%v5@7#?wDj^{y=OAz4KYk3 zm_(w1n~?Y+W(9Yq?N5Vh2jp{IOb$PmWoHgqx;1JV?ENA%WnWcFgxhWof30ED9FhvU zef^6_$OO^PsGznSUMIr}GWnsR>`dhgNTBTFWrtzTrI%q}gS7OY%g;XOI4>MTU#iwN z;*S#YAPD;O%EsfyutLDU=1RAs$IvHtf7pVC+U!pGL?{Mxi=#oW zN8>wal+S)vaqLLv%Tw7^V`8qheg=1@&U>n10ol7gm zYX&UoyZqo`T1WaVWnSYNR6<1eH_I*&nLDj1sQ}R~>d5%+3PNmJqM7ioe}x_yygtg` zZ_|;P5yUFKPS$9=P&Wx9v}$uA-MJs~`Kbr`%ztNPRnM0!vO%!gH1!F6n@a7di|Fnc z6W`D-s3P1@x&46N9<}>pONwkW1{1vrf+H2BfYQP!i)n-A9WOO^>jT2K9|4kMM8r*W z^0<(c8X$g$Nf^3uyY74?R{xPb0CgP(KG+f5aJ`omFonmO+a2-s{oGsObQX*ihlC+_ zxk+W&iNu_U^vApGwZU!{8MCh@ReVmmJ<@biC`%i|mJXh|JE>!0veg$L?Pi`At5OGh zJaXY+@S<3{=+3GD$xC1?8Aq*$O|}n}77CDwYH92y<9RD5L;U(+%qcUogHdLfy<|d# zSp&CP@`V*7h?#u(S4zi_{%Nz4K$&q~Z=e8;vQ#x0Z4f){_|g1Xz@uH3Oir;jiiliJ zREPLvVmg;eR$PVoW%nsgow%-@>P0sMuvj)Ay2~pisoS8TbhyJE8V%M1Df;at6NPwm zUkF;AgEC3x*G%7#(KHZ-rlH07_V;9)nX52y1Dj-@GFI-BGu6BWi3DW`j*$Fe!IyiQ zZB&Kb&tyq#zK!T7#cLyMpMy&5X~8uDpj|fFZ6NMIb8KL2R4y(Esz2iBZTn$K1R##w33sG-dz1j416(5Oaz&@D1eXY zS@lxqEm5Dp7(u*sLmk_yhCcN=PsiK0#5$7HMH_e^d{{FAn5R!%wtV*85(MBx8{jfL zh7)wSqG>c_>%aCcJqI!KKbhh&X{)0*#TbdT~W1%>k>G@jm&nCOk_D}_|zyzI2V|;%s9UH`yD8X(~8M)7QUuR4wpK$FCNL1 z73Vu$P9_A|1T)+0!g;6qi!~;!+UOLqvV|={?O$NBH346j z$P(6)-RVWgVD1JqB3x6XM+dlz^teorj8CDo$N?RL(7KwrGpzjkm7Vk7Mqt zQMy^L91TJ5mX6==8^(64yp4kqy2V{X5N=&w%9x>u6l0z{hlo~NMxgH@#e?Z_6ZqkaMLlMi!gn{gf9Df6&0AOF+}{Td(_ zQ20TaErghuNkU=j!Y#$rLm%5Xf_@lU(46 zajzzvcDg&5OOROl;Hx_L%%cjiX}@av@?}Zfrl&o6uE0=+_M^%AFx_fYNqe2$Cz)$U zS;}<1wOnZy^q;=%cG2B!aJ}aF3eJrE;hhW0Pg1L!5>_U~e$}bt$&F~r3w?^jMySKD z9b9ccwSf30k^YfglXwqigMTbH*0@w*y0A=K{xL+GYW$7+|HIf@M`gKu@5AuWDc#Z_ z-O@;RN+>DPAl==aB1kt#N(o3eNF&`L9g z6A_QV2QW%m98pd=#ZWS7fuj?`Fa-Ytb>lgGZ(<3Ov9D~hSAGh3dh)3pKkFq!#9&lW zRl?5}TxtJ6?9uD=#5jdv+q`DWDeaL_Hk(z&v!pUhA=wXexYdrKuaPu3@FiRHh+BD? z_)Am?fhgw2*!L=RBNhFIs*+N8|4=H>-HRV_fb^WHX-Ti$^zt7e5i%f_-D%oC8K7n$ zH*Wsw5nzW}dFCP=OyWfH)@=`c7Yyl7WSwIC2~xvrFPIxTC^YRv?t4vJPU5vKb7V4m zC_Gr21?;Jw=}md$uMglR=9@N7V?8yp$mK!$=8yUfMzSc*7$hb1kC>1@l|qUsPt~lU zN04KCUc|mLCVsP$S4mn3C*vnC<$wD~lPE6gQAVEa(D)~5f@|iE(|z(h`_aVf@!!a0 zNlwdK%0RO1>0^eLL#UG_ET!_Ai-qplX!oA~&LNrg*#RjxY>4qcLIQcxfWP{%z*6B0 z?~>EP@SD{N(}s0-$Hsi+*$XL-$bYC-0T$T8_MRx-gVj@{;#AqPW?MLeW`Du|(kxiU zgcPNXpwPgL+V+*MU#=Kb@rT^NBT|EQwHIucEv};AZR~?%I>OI$%jMBjAWp4`? z?S$#ll%g`Vvc6coLF@Pc9CCXUb;9%&hACnwMx_Qogru>2rc0h;u%aVya^(Rcy# zP}e6~#9nlV;y~vsd1Uh=61%l~*m!ReAUk7N(s*~|-PB9ltZIO-g%8O9k87~A$lI{jGS zd<^$J48v>9DHc!O{Nh}M!W&DkiWqk6ScHIjkm$%^q8%{TU-;c_Su(C?$@SPGm0H+; z$)5eht*w@K@sE&d@V4@B6uar3x+tXCmrtdg@vVgg8+iVs4|2UrMAZ- zO$wyh%*S(3W|~>GYgQ7niWh?6Y(^Z2aah;m%roq4 zdEAq@a;bj3iSa}`(l(0oGsR>g{2Cl-=q}CzUq&zcIx9^SI862)K)I}M;7kOvG#!x8E6e>~@g;t$%U;#+eBssS{}2n`tdb8BsscJS*e%GlacBCGXN!jk zaJtu8d$2JA}1RN#ddA}6ZAr)>Yi629OE7vAf`oR1$#x0z5<(mic@ZsylMjYl1g zm69}|oj*cS3E@8s7_lm4w&4Xlep|>_e9*yOtOu+}5(yx+e44=xpA`TU=`K6XQ?eL0SDarYp@YAp0>|VK1#<;y%ntJ zB2yiF7Jx3pwX5S0{r>2WF#hKikXFj1LYuk{HRL$Viq*fBE`Elw3zdIK~0vY1?_fu+NeX>@KtG z0s^|0uVr$x>>9F3YN!DZNG%RfhZGNn2Z1LIc<+YWG+HSV&@xO& z8@alHDU86Zca-&?Z$eI|@?A@+bUoWm3Q>=lc>OtGd+EjYChrS6&EyQ2@9^LMm-7(r zAvxas_;Cp=I4yy7LFqeQP~6y?ZN9T|MWk7y_I#kwYP;Yy@ePPbI(N3KiU4ub>7|-q z7k1_EY)#$lrT`rK2hJ%Vc_!fKi0+f%B|EHIJ7c`f$axAPwSC@w z2Sb42RJZsM6l+VuH|zBFm};*gnFzWT+FZDQ+z4P?r&Lt# zcX_#fyjbkKibeGz1L;P1Yp94hE55mDK&RLAi)!P(aUFWybQuWGtnsRb4xJ3joIz*1 zibC}r>ux0z{H%`$_pl?~NR@9!g69MhTIBFJuor5wMC+3p623 z?3R$CiVVi{RsKK5ofBq@x$B$xnGliF-P!nMvM5D#9vSBil?T}oF{`h87WJg)hDCIFJGlwYi*e3gkjmw z04Jy!zLR;cF>Yb;Q(^Qo-H08gXE1ihELPz_+MajBWsZNML7!?nQhuua`{<-K`FeE+ zW*i@&^EC>GAik&w!)$jc0hkA`!)k0_q!7M4;ln5~Ds93wUfa4v$^J%-rt2R~eO>jy zxHrdDOtS#*!F1U(T9#dl>gj^5g-+4SUf_Nj~%ZP-i5vCrmggZ{7z!nUk!h4DwSL=ee9H{iM3~(%8R!w(t+* zq7vZM@!p5g!k*eR^VD3u_Jghropx|rT~Y82R)QkE|Nr1wph6zQkoC)XgNdVa^(0me z;^axI`eQ86jn?`1&$$*BPcWi>`)wx!)%qLBV9b@T}>M@#)Q z$$CLeGK%*XHDH1Hmpfe}<1}$!Tp|3t@w+W{3tGhf{^Od4ZXAc+748vj6Z^j)NpR3d zofzz#Y0Fy<*|S;GzLibBV;1?A%Dh10%VvHCbhp_)ow+W+uq7~Th(SWimR)KN^4nTK zgghSiPWg&V23z%ykgQq55AZ}IPpI9wdz3v| z)gs-sdhH8#0}|#F20&x!KVbsZ3O(MI{C{74|D}FK_fqm?t5(}dDMo<8Q2S7OjfCBmx3mSV~axtZdISD4V;*#0hljq-{(U&aK;{aPU!z z%>#WW>Rr~gcV@Ij0`%-^tWpMWHJ%Q)yW?y~^Jh4f6BHGK0OK}=hUi_2T)sK0BGGDh zg4XOZpK?;x(#x9!9gQ|5uJ^%^GQUqSXd?TfMBwQ}K``dy!Rm3%rbL);o#rLT>PG-C zi~Pru5_}Lt1*N~eq!vHf zVqwXU;Ki}#R*5IO{As9nLVz$*`NeTlq{-!#sTvNz3wFMp>@QfWcS$B+jIt*d(t#@{ z&{*wB1HzUBN*iwWLgiP zK8c(r)gDBVxiw^J6^3Mqvnm7c_5pw9Pc$e~l2cKQQ$pUR`_Y&T5_|i8IBE4=Xt`5E z8*ADNdc`0RDV&v0k4-;$JT6uFEor9JnDBtSc1E4!JfDwKc=(S%@O{c)T(8HQd5%^s zq!E&d2*}>L%r8MwvT8dkdKBb%L+JOX)?^%na(#o!9;*KA?&AGDj=|%_LBIomkZlL( z<(Z0{_iN|FY~i-x;x{GsJyyHZ&Ip~;50j%^htZW+FqC|`*aV~80GxS|Nb}~?|2l%v z`i-YGO$~!5Bc%KkiZq}eBRP6Dj4I^$KSFBy=s3Z+OF@u|l98w}+Y$Bhh8^jHu|ao_ zSZ?o)PHA8>K+^*x4Vj-aH63p(j=Z4eyqY>O{!Es*rdW} z8?r*KF8cB}8{iV)KO+~}*Ck;2`AO40pSeO##{wg*L@jpcvu9}xq0ZMQy-L&oO`4ke zv5TbytGF9@MHmxux(X8uf}&XoPFx&(zTbcgj(;0D&@?jeRp3AHYF)`UH*C>F71VF2 z3O7l?jWNHI%=tg1pN_Lznlmi=;KCF@dD@k9~uO!L6KU`OJo|#TEEpxa?&?SS~9mWo(ctjhpVm zTEqkI2T%uO=k7dTFZdXG%olWhEB-WhFQeV$dmgU(;JZC)SHQl>o)huaCcQC_3P)8$ zt;l*`#$636$NA5Tn7tgPw|Ac4xrY_!Zl+>s`HTERo0^+#n-^jCC7zG3(OlZmdU?7e zrzCi`Jh%akvI3}P9n;joO)OIyU5fjQr*SrIZQzy{pUL^+)go}~B;-&@B}XZ^hR>l= zrmE3Il^oa8H|>$k&-#*L?WHm-aJ2tVkeWT-JTLpVx7hR!T& zalIv)i1YfS#z%Ie{ zx~1I4@c&OwKw|7gT#Emi;TR_LP}I-otHrJkRga!oXldKse$1mr>hL)XD+kI6fkj2n z;YS*%O2C6fqVy?63pZAf(w4gR`=j2JNXG-R)AaMEMz5RK#hkn#Z% ziVM$5qNdhYXcw^Rt|%@wd67RBtE3m?2|=ny%yuB+1p6vg|~PXd;9xdhANm7 z(afiXV|0ce-$nV5U@zeM8K^hH$%$Y{G*S`n>3s#v10+#6=wz2$>O?1Jw{Ng7orqCL zFY~F`3#Mv4+J;%B=l6mAW-9M^%`?=#4+4jkihgPa@21r!u+~kROK^6`^&t`f;R?_+ zNj54;lA9$=n{?5_ay^@5_Kl$KA{>h{rQy>A)Q8gTSuumnA+gZ7X6XSY?2D23r71Fm zB24&x_hMsxiF9ai5dSlMu|uSLF|*GdvOJew-w|0`%QUs%A~G}B+if0yI^sxK1TIq) z5Ol=T$EF}{6I3hx63}P9s0F{pBt=wtUO1l7qHO~}QmG;5#yvZC+!G+siycn8wrn4q zonyZfU8zA+gf3S_2M-*`q8)r(lmW~+*I{M zsSLc&f8hLiS9s%fZ}HLZbzG}wA@UN_nN_*C3+*oq%l*XNR?c&QL;Go-5+>}jjK$KO z4Af-{ZtpsE4WU}d;J0EvUnJ}az|!by)sn;wzVILVn>uoG<|$?JnyR0L2Z{y1s7SwP zdWL#`HRC7dxj3Y~Mcw9b7vEPe2rr63o*XU5>w+ifuOk%vYZPqS10;&+F4m`?D_cVm z+(=To^xee{lwx!=C!<_E=Nvxq=E4HccJytIK;^&h zCuz%P10XpzC6ai?_iU0#5306Byy6RC@kXqrG#A1@tRBnh?7A=dEZvwouj4l?mS}S@ zS?b*~oE4-nH@lM#y;F}ywDXus*Ypp}R~8SBmIT__OwH z3BeGcT+H}gosD2gmaZ*e9XycLC@Y`{Y z5cpB>VkwQis1@)TjI`*>cDzQpcLO>7fu%#758&rCS5EH1eCtyqZVC!$sORC z)jy(j;H7n2q>LF4L!;)68xuVG>AFzlQF!HJQU@ikQ_O!|bg@c2*m8H0Dh*H4t+G$N zv2Jawp%lN&Qw(0E{&246hdTeX+Tk(+&BtMmoG+T_!!QKjZ&4CtJF92BU5nNrQX1lM zWf~h4pX;{iot$su$IrUrMI=1-RZ{TDf7U;}1#3E}*gQ80$oyqduq@3mfY<{&59zEU z0Y7O?(J`;g@dic)Y+&lkhzp&U(?b>$hU`2}TFr<#&%H>`WwpxF5E0J3H`N{?X$;Sk z?ZeC-Zc~h&UM=x15AAnG>Q$;Y4O+D1Zhc0t1CgrPjG=7zGAZt*;eR70Cvg6H*CcAi zTMu#;lHGVehx82K+>~p0ccv>&b8l;x3i+EgIgWYur{}kU`1Xx>4&5`KA^9p^COO>>;N ziHc99+^PAL1yJE`8B}eI9Xp$3L-5Oz50FdgojD^xux}SCDr`)#4YE-xxssh5+0^GoBlXc7{x_ zsxL-`!*0pvtbQP4)r&1Xc-`P2(k$U5H{al`hX z6;2Y7R|R0)f3lS}TwVbvX4x&Gz2Je`6rHP9VY?~u3|gX(p8D)YpY?!^KAuWGa)Ks0 zq+`Pj-S$q6Z^wqLxeEk^E+NfHny>lt6+k-V9Nd0fw%1EdrYoY)FLRxIRktxo%&1)* zUi|pspd|eSOnTHl#`0^Zo_$#%sm4ro2xa3#jUzvvcmh;I>uQ5LnDf6dO;J(x-Xa@f zcE8B|Oi3X(t%G|>s_-*mea5iqCuR@sTcBchweg_tZ62LPdZ*-D@8X=Wu*QnoIfkH- zbBIPpdcPe2Q=LF=7R;Fj>StpVy=q=^RwBH%fzs=K#sEGe?k_kz4=sM;yx`G!a0FxT zQ{Ub5Wzrn;K%y_wCL-O~`yc;D+DuaORGTX_UBxZasD{@KX?(mNPUXLJ1xg|GqW#I8 zy8d&-*?r?^(2(ebAAe)sFG&ljazqp7iTkPdhx5|vkukGjzOplo^s|e1Cehj6Z?k76 zQ)YwA$du9G?1X{hkFw_;MNdt+1#vBr_%eR5i7UIm?FfX!67Bwmwm*}`IEjEGLhP3#kxb1befqGLJT%5r&4?h-B3TBd6F)OkA_ zI5_0^FQOrXVSn{HB<^yO+o!e@RfOQrf5eF`w0{zS{6>w1&6#^OB*{(#Xd6Y0jRU%`<4v#(En!+TzFXg!MwE9zSjYBthIPRwl8N_b) zE(lh}Eu&%`&Cw^|O4>&~Q>p980zXcm{Ef=fBip?QLHg>+aU<@=$bb%p({s?g2 z&vqp?1&LW1xGNeqqPo6w+YPxVQ3D~xZ@00*z`%C^Qd8oe&NU4*b#L)IO~ox8vPjoo zKe~Z)?csrB{}keT2zyM&c^%;f4yvgvg)#W*wyp2C_NITS1oO(f+AXUz{k#qPrnMZa zR`NO4$KjSK+mN!&Kp@8n6A%(3SMM+_8KOh&vulm_f=54Z6XS)V>=p7-;;`{6QgvSj zqJjI;GqkjkP2_Xbw`X!vU3o@zt7q`O`DajKI(zcGbJ}3@D6kqY*q7;NB(9X!$bb!8b`_(2jHBF6EoF_b{!aF0L4evPg1Y%7_; z7mNw?;z6kV-VH9kMUGKNscc}uWK22sFfuyaR{B0Z!Vb8B&J)Z7n?n@(-Op$pn5*q} zwJ7V&|G5F^{as5%AswADe_1CtTy1=x!bV?cHucR|>)8d4xOCzDhaMIM34L@W6P#l1 zi|vb%U(RniAXdMqZfM~p*{YN>q(g7K0VWYK#?>Lj1wWn%N9 zn4j&fv}S;Lkgue0rrFG;CE%2cctf#@BA?2aT7Tp)TVf1kHYAq1*F%azIKfyrQG1_S ztlg8s;`d6_;pDeMvk7`aZ1K0iPQGto${!%<_kR8v;h)Dn*#ifu?;Pq8(KqovVU-TO z-qL%pH=l$9MEZ8worZa67e2D5P`*tO^I5G%{9*8ik>X2Gq)^^@)^w-PIyf2e1%#G5+@p+R=)TerP6t#|bxT7|wjTML`Ao$AuU~ zSC{6gYTaglx#l-)1UKcAX=0Sfy3_j)tGysBU%L)86ZGKzb%yV6z-7Qnl&5?1`y`|P6J5h#%<_6rn zkLguL=+oRUr)*~`yP)FPS1|*}eWw(s)Ws~VYQI`p!7e3^_|6#(%fIXP01B}BGqFS zXU$x`@Gg^Z*9n>-aRtWvE)@{t)_rDr@8#^0!Z8X#$DA)#Md&p5427Xbrhs7A`jhN_ zSim{(UD&RelW3(9W|BY~*)W2Ufd?ZXu?ij%@J~QLFmY}2eEiRLyACX3om)`3lo(im z=g@;v>@{}oLg0D8ho}z*s?@*X`dTX;^={Ya>^S z#XvvOlkzurkjzO%mtf9gq)GFC;wbYN&Ftwg`L=$1PHFd9H%Jd!<5wrYrkY|U821At zN_5J_TH>H~jwnanIiWFs#_@`c#1N7%(Pqys+b(X+L8P?ZfEa~W@&Xgqx1n^?oIU;v zr!4V}fu2YXnvs@cB5--(zd!=vcoF=^CvfK&Y}BAD;qX+6wM;1+*lbtazZiV5OYLV3Pb z_?c;E;tM61^XLvZ-%l5Cc9q)J<>}vTIW#u)S};QUs=EuJj*NTC{<>2jjAtCBS)x&U z`dYqd%-*qXLJG$VFWjhazXl5;z6Ff}M2g)$bUOO=GkfY!&ItL4okC8+kQxPw_cAU* z`TFsg*g$DMlzzd%^K;RJyh2GkxU4JNpaz(MJ&lxp!-AQ1F-6y?2YIda2RFa>b58wc z&*;r=LWCev*0(9|^*W_DghGl@aqb7@{ufdxMx(1=z8}ksVcpfjgi^Ne*0% zd7w&(-}Rb)K&y?MMufI(ei^eW9JJ>CZfU)$iBoQg5X?D5y0Z}d8;5*k8&-JTuV(tP z-#xb99i;8;3jKyFODo?4V}F3efa^O86&cbtx%i18DNQ7rkn#ofmsmqn7N9l{*C$X9 zXdM<0i!F5d^m}HGI+0Mtnm@*hT1~!Fm%R%lw%x^foC`!D@sS zgh4u&%4o9i-1Uv}l8vh}AZsBhDh{sU5Y$jhKomkffI6`}0Y&gyYV17T z-ZBu+yR#efv-of(xS20b1)B7axq8YtMvKK#4)PLzt36`@3_QbML}{UP{$*BrvFWsW zI$qdW$0f?FJ%#nuOI`5rLmJpXNVG{=@KE1R)2HY-qb$kZ1+H1}6a#mTfx(6?Paolf zzFNie;7>DF-CFW@UgW;g;E*A_vD|(U%knO6PlB`5T) z1ItlV5?$NXh7{{KK{8N}hZvYH?_$6kzmA_s3fr13eW!Q5;_zH41IyKCQPQ`R1ArfB zfqSChmoRZ2Fn){IJ$!u;uAIfQW!r0^K3IE5(Doh-zkb@k7CryV8#`i3T^K8aq3xK+ zck-KV0Tf%svoD+8{IwjBsus-~*vSeHWHyl(CcTP7b(dImZX%R9s5cOi#z0B0rt0tvM73-v!nNzw%L4g?g{j0tzc z2~kH|xvgUa9}``$Q=xtLh0`p3Y8h?>Z{lUYO-rsfo&6W*NV!a8HFCtNIG=r}IiV=K zxIJJD*ZM2s^(2=T^vPn&!C!iaL&(gq1c9k(m&@`*@g8x7N-1x`sj+6bEGAoMwxuE%66IWSia7cM5VH zL|6G;uHQe*oz59FHDU}1|05(&++Q~%6^aB@eNIyl+$%ElPo0{^Yq7rrYEWBRk9vYH z7IWt$ZyZs+lCa1B`c6M5y&S{!7w3j1^6mzFXC2qCgl$Z5T9Vzn#yqNqGY6BtnTmXR z?_}|q<2d%u6+MlYpT&66kiJLjBsEdEja($ zEON5p^CWq_=}~^xbC(dbSIEYIfg=RJFC&W2kMP-i#3=*tD55~kMe=el!mt>zKI;q8 zA%XoI`{c#Y7=YMo|( zkV|j2NAq+24Sm&h$gt*b6iA@q9GAp)h&`-oJ+{R^MqQc^G+=V#^|6ROmq6@&x!&?^ zE8ypx(8zR&i{G$>Z!owhE_sm>M_HVOT5fOh`4L1Zp>a&XkU$%kkCEew^Q(=J?f2bH zHs!Z0NSJVIhAdtG8ya1&Y0wT?KMe%zZp{QqdP;?i&wTicbG7|2E?QWK#j(lGt3ZbG zW`AabuZDcEk!)$}zxo2c$$)eDKU>z{Ns%wVJw>Nlu>9ddPq!P~Viqf$OU-lXwua>C z^?~>oh!hRm(vWuTpv=|yWV~sS*mj*BK33M_RrbjOv<8FY7(YO|$1}W^UOg}5oL|^A z#|&_}neDf>8Q~0B!xP>v?$hOGU<)@Y!j_+;Zl_bYTDM?iudWOqY~-I-XBb1OQwut!`X$%qbK+rxr`NsG5ajjql*rny>1w$f(d3mq0*a zhyd@Z!xepViCLZZtJrnsRZ_Gy44W0&0}Z{t66s>qO~9#swLv#Q=k5e!#k_BYG*Z() zdsbax%Npce#4T@N_nqcmw%ouEP7RfEKoAw8=vONbt>@FFA*gc4n_6CB=AFBCy^oKO z+~G`(7s69yhYgNyE;3^COTwV?(!{90I6@pQddY?YsV)*3JDn~1Sh`CqzQL2G5u@Cb zPib8tJ_EuMw({AHo5=v>%3hKv@wA_NXhxso0c7?z7H?%+cZJO ztdH@$rBwJs+RILw?;jimnr_8lm`9b$|92ZbcdP|f*+xnBL1U)2iu%06m!MYZFF&W} zcltRPlTti1IQ3<1FcCfAz$olxbT9tImGdq-$|ui90|~G^q{QA+*};5uoGiI!v)-eh z{D77VLAMh2#ZH{Ga1%s|Fc0eH!f?%xv1v7&sWRCmhP}+S{xNf zOQ6#KjV)*%Z*dksEM8^vg(!d!##CZhVE(#F0vY7~_AY@x@*!=O(UI^!Jk;2ik%wOTbe}T+na6`AKeraTe%9YWn9Ra&LGCRt@Q9=EhTqMJf08qf^SK3);;7%v zbBVEMJGZ}+waiJxKOw8*Mz})`@<8bi_Gcdi5*JWzkOk2*bpf>J*IQXV#aTMA>l&P4 zg<3v8d26lBv0y3N1P3>Oju!I(g+nD>F> zKDNMgw>qmc@}4wK)QiQ=w-9~{2eF3~YX{nZ-2oh1%svfGbzi=A8eb2Ve_77E2OBZ^ z_!bE$snW5Z?j;-ZWf~*Z^_2#d2yM|=!aD8xn>E_a+P>o)@Q3~{c52{QN2yF;?X)1h z)K(P{lAbw&S7tz4D5#fZ5^s7k0=$VJz-=3ex8;9}Q0*jOI`-o`+*~3V>p3*t7zzIG z%oPm4tK`)jyd~L&4N3#XS(mfeQ@w+%LZjVfir`d=DH(83Ou+ViyZU%>tUlp&MYBko zIBQAQw5Bg9KP5p_IzODU!Kl%sLkNO{%+D5`^&EBM+l$1+o3+>bQ`aF~Ko!>M3 z;6EE^1vVIE&_%>b=094m$S{ zOkrY}fkYrQge^}JDfZCDl_e)(D9qRg| z3*u@Pq8i0)x&sKFZnBj*Z@3uJ^wcykqjNiJv_75;FHgRbMZ$vr+;#0R1DKb_6KU~{ zK(01eXK2WVj5^r%^Y7@QXr2RE=cr9R=!sr1<*5~3U2RWI+69T6J>V>O34ZcJ>%NL^ zk7E9652x;j%LX>=0g`tGVTEpIo9$s2xwW!o!|^(2e~HK;j)}KKDNW0PF*{H+x@)c+ zS*)!z)}5+^%O9*ev^k9RoU2p9-DR?8leWwMA0dIvlO7KyY`W+Q@qc85BdtKKN~6Ey z)#N4_``>szYIBL2ew=GP`TR?0c$Jm#l?9ua~~w9S|J~NX5?H@f44bii=_a2Es02Oy5@oENQ;_4AmPhk^ zp;@XSNBVU9lNXqCVOlA1OWb;k$e+@vue*lXIv^3tX_g^O$~LMa`cvfGA0b(-g+owQoIx6j}v< zf%ad2c6Y7Dj=^uEM7)3{8eqcrYf_2wv^0}cj7v+2Tm>5#4%0;TcA6%`TEIRRns^iB zTHdXl{~XU>vpUE1b4S#@)b0Z$lA-VG6Y@{GgFiyTemWwi(b5{DbWQyM18?G3RLo>m z1R|xXu*Vreks58l*7^t~dBVr5ajd(et#*D0CEHh`;Z+T|fu@SHwymOt(~gZNpMO9y zapniFO#kE2S>dT8m5^t>y!V_RHlX~+dFYF$*Q!|CeaaB*3rD6)O+0-VK<~}p$N$iZ za5IhHF2LF=Z~W5a%pLsp4{$005pYS|EwTp`#AHvWNgw8oX^{Zpc;m9(+X z@Yz*8S1ALHSIdXC`}|#&T_FyDRUP}Makay5>_a9{2R8Ph%I!CH1$*a4wI4PN>C7X2?xtN2xxfcoVJTKKy|FoH2eWeq1bB5FX~tK-oAeNH56ruP$#8%dDc{o1N`C z*)3iZg28n+$K>(KOOkSPWPv26stWv0@Zb5fUS&?#+qZe1Z_pYJqz5K-8K-RivKHse zVC75jrBT!GI|E*b^T(|tPVU$4gsPcP4>i;Z-83W{$eW@`mzjcBh*;CF{2v^!IO_2Ss8GE=_|_jifY61e6EoZR7C^BAv14 z6ty~FJ{y1fTPr!M+lP1Qbd8F!X+D|3*1_eB)?jUcM^J`3+~ZaM7gnI66{P(9l1PL) zB4e01fMA5S?kQXZdhPmaNEqHq*m^M<;JOnOLXRfo+mdPQO9IcF=m#;~MIv)t&3ks>dWDhI7t(jkITU)=u^M3Mp7bd%Tz=JNf=h z`@yM@KCizf3Wa+GF_-z3c9UE4=6MiXq~!r+;366nCS;Q$h*Z-rP_VIPL>3Xxn8oU*31+ts!h$* zg3W6XfLkavVw(NX{SE6KL-!Q=%N;zr4M$2|f*+TnD*b=;oa()K(sLzwwC8DLnWPy# zLjwp^T4tWS3Is8mBzUq6R0Hb%dfLku1M6Q?tm34D#Pv%~Y)&d*(#86zShQ**NX88S z#;pz(1NuluPlHHT3Ma%3a1xjMR0=>iEDg+^XC!0m-|rbW34aLaU@vC7APt^gRXvL< zTVh{se=iSBA+Z)x+<1-uU-T*OAIHr*3DEss{49NKdkcmV+d|^6cn*hzh0$BX7-G6^ zS?)q?BPq{kjaC&Y2&pS^2!hXzzdwL|;M_w7VkLkj-%CyN26dsu3;7FUkC2a7v#(C9 zg@^cP@+5Glgu+rK;xCLnjb+zacC@%@xM>by~fGCmsg~FsKvJ1_luID$ONv3+D>D+S%nn4gx~9j-TxxMf?#A!Mi?h| zCU4REs80CX;2g~(=a7;oko+F)`AeIX0WMMqs-R*tVFyHRuHvQm4^5~bG)MOj}i%U)oK>HzgLW|?xT1ZXa|ijJHqt+jOU)9SkL zO(JG;63(z=a9&e@fewuP^=??Ja0Z+_^SZ+yY4Z0S%f|XVf7J@yQGhvzw8cgE z&t)7IJ|A*McRNno%#)^uo{wfGGQmE=hv8T+Mt8OJSJD!|zZ>IwuOV|x&6(;)dKmEq z1feqJf1g+W361X?u}4f3FKwW*xagF-EpNZaJK@{RqqHvJAtRkAAOn@TIF#0s1PJmy zQaIZ@STA(8(RlEYBexgq-QCu>BAMUx0{FjpQd~MRch^|-87Ec7&EYh92MFLSW)5qP zo=;(&dn<$So1gX&|~6YfNlGO>C4*r&U{lq zs_@I*Kx!%)OnN46k)k!i*7pdn-oNc5p8|v`DaEb=eryVV3GHR!*rq(%PNNfy0G&0_ zD8Rpy=7Bi|CaM`TXLtR)NWIH()wR%FGs+p6fjfO3h1Fyk@W6C6-F@-R{~xADKX$cX zu7h}%xb-}}lf09o)KGcSDbyuyM>M`mT2_i$2MTx@>-(bGAg8v3f59Ae0RLrXrCKo; z3$`^E{kYn^{Zk>JX$$m|fJpW#&!h!v1@~L@sz+m$AhVTATmAOQ8kAeaYK$hBrWiYU zmrK>z>+UA9Q_=m&7e6R`v17`aL#GK;tosnB?nCjzY!|r<8Fi<96=ttt^R|cwpY>L_ zGdb>8S+o$n*@aL`4+(ITDN(?z+k{zDawCwuPE45Yg>J~wB%+`hIXXOGJ;Hr9$V^{RL1&U= zxNWOsJ?OU>hRB*<_1DOKlza)3R5~69PPjT+8lu8%D!r-!LT=D$S^rspkL)maT6l0L%63!$anDX(CtjEHRJ9%YksB;vB3 z`>^srx%@^Iu5C&w##YndqVZKORq$`?f;MaZ62^KMuBmPLRiIn4m1fuxY8#gFd-;d_ zS^9A~Ej9C%&-yj2aiqGk;S%J4dDuFYKF{I%15PHoVX||QMkFe?SS`zm+iVkzl{|i> z-z&soRyO`PNGScdW8UjbsfCc2Mng48L43WM6v?}1_Y1}E(J`rMK_Z*;1E}o-{XvQ^ z2G`(PW#9021&dO%ANVLelLiuIdI@ZgpSF}lEw&7Z(DcWvSnPFl@44|ry^2D&kj3LQ z07wMg%rf7o+=w`NQ*cONMDw&cp7EL2>P_owUS3V+nBQ+whD4}4P^E9rUFBjnZiB77 zWg1ZReM?zw^DMuCHUt_%|MG&K_gvuu#mJHy&mdn>42o)&x{-@Sd+zT~=LuHp5Wd_1 zHmeH_tZkUUJ%m_-<5P=F&c!nv8mZ*(SC~V7OD`=kfP@k%QdWbUalfF2X?BeG8QCud zcg&V=&62pdTP7qnhJ6h8sSQIxkumg?)=r_18i!~+1PT?78%AP-ia-G+Q}20@mPE+d}qKqvL=(n3PxZwMI;V9(?_ zGa_AD;R~&V@a~7g$R}iOUM3Aei(0P`1NkVZ^8g*jiHR2B%x%*-G^LBtxP$U_%)BfsLoBIF;( z4iwh<*yu_eUY}i#ln}L~n!Np6`F*76j?|lwVqKB{9z=P~{9Qj<&&UJQN*pm4G%SRS z^n&`o+43ZMDdX#_O3|WVbBM}`*s9aYm7O1!AB{IaTjo9ADXGc+;(Y!3ayNO*%<;-i zW|B%cd-9Wyhs5d+EPV|UG{POtBT8Ux>1j32=wA?7`zXjzIwNrBaH)VMoE)4MizioE zGS+1lInt%XJFmVIcq*zZZfQIa zUAaLDq)4Ic!vtJmucAgGluS)ts1-3$8+bl?2`hOtpI&>EZuGRb5 z?S#^*eIQaoX5qN%-k z6Nk(uaD(d@sq*oY(%yCp^HxVrr#B-UifNpvVca-s4Z zLxQdf#B@0170VGVZsW+^3|VOI^9YH9M8wfJaJ>_Cx;&l)TPrQ+Y-g1;ske9gSE5N& z5m>UqZab)U&acO_4H1`Rx_Zy4y6W9r1RfH!z6Q<<3&^KH5$!)YxP?TZ%N6+uOZ8Vr zjSdf&@4}Zn88nX4ivGl@-OkwIrH_!v>fXpVW+xUgs8O@xeki=#T0Xl)vW~|`AyJfG zb+HGofo&{w(YVf>~16rGywbb@*j8A#cx!mpU*+4^{1Sd?CXkG}pT4lLFd)HDML)*8hd6>4$&9N}!0(a&zy~-_nK}5sIs84(6{? ze-IxIe5C#H99W^IUX4tOu+tz{%V`@Jx1m`=k#Vq)+|SY$I> zw|X%wJYw#kE6iF|LUei2r@Zd(Ohb%|HSsuf^erjIp86z7u#;A;1mE<6&NI<>LGyT9 zx|sP_v}169a+LiQT9XV-8CBxIi#L6&enqKP%*wd>&p-IsCtPzkl>=S)|3uSB%Ytc? z8f{$u*L9I<)s3S7=c_};EW9dN3!bi<%96Q9NS0H-xm3uukVP245dq&pM@q`N^{5RjG*=@g_JlwPmv33%)5$XWQD^$iHgIPDxHkf?4`TZj8Gg+^w-y^msfT&5cMaA3 z6GukhIgW;_$b%}Hh4g=v2?W9b&%}K^o%n@{xjzw|1c@MO6WX!m`oYo%AQm8k0@rv-p#xk zhG#$X3k`AX&nVB8<|JXe$_@8;#ID_Exca{E$TN~`W>Yy+#geTZPb2#{+3RfI0twKbn2pvtw=9HI@x~ATVxF7Kln#^ zDeZ{!=DFfCj}ArYfhniPYzz>tE{pcTuDrEz^;*j>gW4XV5lNKP&>~-u!3_h86nDu> zPkGG@HI%R^{388KF1g}Q4&3zEOeAe{}BX4l?@WN7z$x@JuUyEf;eFZ2a$v$^1qHhvl<#IaV~yNAVFW8Hi0Pct73I;<~st+lf5PoDaDd| zMLLnd1coiYE>7c>X9z^?2ywtTNPM7@cMPiec#0F_+(38ziP95A#Eew{ z-Sl}Th1CloK;y z$$t&OG01y0FdpFEsVo-|qZTOgxJn)D?&U>D6RIJmDK8kv*cLSv2i|SHAtdor(s=En z=Qen^-6Md|t-5|jik@{+)5s52gg`SQm<%<3d(9)G7A2upj{6=4wGKis)#E&QdeC6`rYPpRGv*W{g z>vXI3+cI1jQ=+jT*mc0HTOdz&$ZYHrG7vaZWeRBw^Ai-WRy*ogBXi&jxN{Fo{puD| zir_)rSB5e7Kgx6mKaBQ@8+czNpwFnS2qQtL1IES|4)KQV&3A`(#j!Z>YWYv5t@g9u z_?Z_^BUm~n6PsV)!NZ>*HxO(*Yovn)F?f!?1cKIX-Xi(+xnF5ZV!uh@YS)||(DVO^ zd~5u!z9@z+9Yghhl04?YlU+t;u6qsc!lhz0e7DOi)gGgV88pHxC6yPu4C`5X4YzR_8I5hu3>JHXEKY46-m4A+5crVVO1OO zL_*Ug%7+D*oca7tz0uE5AFO6x;uq35{jJ=I481(RHQCn%wINXzKZYvP>GKodC7_hBqOb;8=VpjJ0AV!F@rCq+V&^Ao_D-Xac*+>Qe{*>0ZlmQtsf(0)+`K& zBonA3yyBK&Y;D`5L0AoHw@5M;tOxBO&vdBQSl4E(fkmqKGV2i5PcojdM>O?jr0!KA z<`e%wfLl%eHeiM`kPVWN@ShgUG)I}KaEI~9_%^MyQ^F1XLS`Sb48j;?b(s+qS4shH zrwERedJ&!JWGHeNCg0a0&7Ck$xvl6M^m24ER0yKnd;v@pmU(kPD}A5Po7{XgED^0I z;Xfln4kE&n+;th=GId7afMr@R__9so-)xQ^-gl3x zxM`_r%(6aaa-X7?c&g);d6O4ao#$f?$t9~96Lp3X02M5(vUih3p973mrd!(S0;`GP zxF{r~VLu}a5M_+R^E(2!$1rYQ6*6U7t{|3oZ7J-cEYjGzLxUv;OwM$r;!pX9748#n zdhRpKAc1>4B$1@h=hqf*BB2K*%)_J|_cEk`B#;|*h)#j6CavyPC_fC^5W!T52%$+# zUkwzM<|C_Vk!Be7jbGWERgY}Q;7Q*)ne(x*cZ|RrYEYSbP7NC`_;>!B^uAjSKSYb3 z_YD)tN(cTngz&ZxS^=^`^l>(SS%fi-eDb$zEV)nBxA{@fJlA|8}Vx(>Cm1 z@Cxsmo0ba)Pz2mMuFX3vTD-ZF_4xRXuIgdF53d}Zl;RJpyBFYkSs@WtH%7e#LC#KR zwyCIv9(25lh^Jkm((xkSBuTkk-JS_U3f1l{xvD%1*{^dMK_r`5yUt&X#-GimrbiYp36}Unm;1`!_JaoHKLl7r)fv$41tXC6be>2pY=C0ohM2J6P(`w z!;PtH)?4nzuOTYfVl9&5ZKrW6-DV$9mNQ2=AYC2)qi%A-16eSCd%tN#UHe?7))qSR zj^V&Xfiy_Kh0jO=fdMFYHz~?h6*}@H{F={+QS|HsA%bWuS1XhkG2?`9XhqxqN*%}^ z3!-@t<j>No2JLQFCxm8(%*YzGSQ#6Wo!(p$(7AP{ZA@ZHnVi+T||aC0dHD*D@B zY(CxxtJy=rK3yiUxU--cp(CH)Zb7=uMqu;pIi+1<^h`2P4hlYO{)aCGHmMXA2QKk_ zXHWBs@$2&R-we6Y4D5ZscR%pVnIEM9{hsJSizw_+&foMohvrtr?KttrNunO^&DI4l-E z`>MFQ(>1jM5wyBbNz%e8r>2l$4?;83+esf}x?1>%O(Y!pqC}IaOo!9``06wA?|q($ z63pgbA4-Hy-vaG|~fS@1z^NyhP$$_L0c?&o65mE2=vey?{vn?Re0LEvI#u7{zv!Z)w<=emSz8 zWkr=g6KDOrBc#9QoTm=B3B*Y?wKhk~EhG|)6OsfN_{yI``_xD4$p=Ij_v%jxfaMbP zLz=NhkkJB9P$Zl@0=e#2pIH5QB(?>^E zO(eEf!wGa8VRZ$|1#yG&VAFp(E*ko9cy#S;M=t%n+g^A6r<%~P6~TvQ*xWy37&7r4eAtN9pYA# zd@daTJWrzz?z@=|>vk#FoFkJrWQ{f)$q5cK(6yuS$#SUyD8aytt0loWE$Blgd#+6`C~xP0kR{|CNu|#ryCjpp0sFdFe>Cq|{e z?N7LiS9&|yg4-Uv1%94S7^=!mEn-F7wL&`mexcKDi+%4CtYOXleiW^<0@XVny+v$J z1xj&4J{7BnR<(xf^>nCyv)*g^zoEv~@X>z&05hA!dP7?cQy6N7&r^dq@eq=&>s9;G zW1-6w#d#EFI3fIrB;x!N|H+LlvlchXO}H3|$<~I5^2$6haAB=!Z34Lz1_La0$1z;S z_)Gh{jCkPKwVWcsIL7!9z}xn!QoBH09jJju#`&~Ax(Zpx*P=AIZjq{=eQT~yjZ zx&5OYEa=6|MsJ_@vs_`fXQ_RB6FQY{K=mL!dzgfke*XNokb3Zk?g4`4iW(X+%d(oh zmbA@>{dZymc$)}1HOzl3eWqj_I|cepFu24TqF$YjfAPJg%R)>W53m{)qg1{oSxd7e zulMEzAEvDTzF+Ht{o&EMhhd(c=Xoh>)ZM`NSlqORhKNT8KM_=t&s)Cv9?_lMUa<57CU%>?)vP$an+C_KAdH(m67^SAq!01Qo31v#DQt@HrWld`MwpNFU*Vf~V4kGe^$G$Wr$+@3co;D=N${*4z z>)RxUsF|=%|(0mi~NFpf{oTJzgBz<(MKC-c(~_tDgN4qM|dz$^RIfk$UFP z{^QjxajR@(?O%&Qs0H{>KwCg|YH+Ls0Eo%FeaqEO@T-$C>b0C)Leq)2G0_-m0ue&qKF@J zH>-ZDC`=Jl^R1^bpJ%Z?wlYcnU?<@nc;G!iI~)t=C^Zc7^m5)Xn%2U`ND3l%d*SEV z-0YDmFld_DKw#p`)uVvixN#8l&QtgnPYJ_3fKfi5y=owQ;xqnhmudiDKsN8>bC8q% zJ11SbH$iwEorTQD*1fy<42?rhIo8YP5C*Xx9kGd&f>U(__vgP^y$c7KffK0Le2;g15KXJT=sl0b`Yj;Cyl2uNUR|4B2l^09g@>lZ$}_j3~wi$Jy);8KvQB@h>7$ohCDyOn!XSgT%Y;8AJia zWJr}jlLBwTEWcU1S!EI-j6@$tRKapXy`=8_19UJIiZW$=gJBrA&-^2+Bq~#NVuCm^ zD8CIw(aqPoXsav`04UE?+2igT&9be**2Hy*U{7eK1dkUJd1T0VsD)(A2Jvq}Ue6($ z97F-75p8$oI&nXRr1e5;XX_XDM4y{D3>e`CDK>amTp;ugU!+&m*Y(oKSp>2 z5M%ShfZ;jbNV_wN5t`QUaYQDv5kgt6Sohm=JBB|0bg(Mi12j193_d7INFcsRR;KY* z9ojwAQVP;j9k|e_H*USa)&bs9atkX)d#z($T*-}tY=agAz4SJiIUKd7gQ&6jNi{Mm zprWqMb>4`$V&(qBOP}Ll#+A>4IbB?YV#G43C`^yhH6V zaJslcdHI(+FHl?@95D?@j3yOi@x>5og;`QCXiN?Pfaxo$Y*d|JSv(afn9C~L&yghi zqjj?Q{?ha?rors!CP4sts5~%eGj&$=206tE?WU{c8;YM=Q+nt!3>G4D1|==WY5oz-^z_e50?EIq3N5%7*;zAkdmmQCR+;Uw(swIw`d zx3%v)DSbpoU+I5qtSN!h<)NAI>Iuw=E`R-^C+ggME==+Ipi7i~m7g#LhGEQ|4bqVs z4?iOqm1FwXNW5yj{piRez;7eT$O83>O?u1 z|HaMyI_-wt6VZoP4z6=Q3l?zX1sbfA&{_P_Y=aMeBQP&Ifl@!lY3Mv}z2UdE+AbXd z+B1T*X$~U%gXWtn^kMVhU%NPdeaoBSR&egMU22=3>j#AkVGzn1@1OW&Vb|;Gttq!=8&$Jo9hB~hex|Lz_rVE-^#N{DQo|F--M+vqZYlPd72sW>!q(EeohFFCFs zE=-Y}tBVdIHs_WcOTTeHDE5dr9S7GuFz7U%y+c)AL-skkwrnAu7R3`yE`pf#%9m;G z>UA|!HqdwwVXiqtnm=9ogl%q}A>M3~&n;FvDiW7bTR4TT)vHYxU=SJfzF$qxOlj1B z0&4dS6$cL&;@>C1A7J0Z#d3$C5E?@OVt#pG@VC?p91amTC`#rvVwz3(3bb-_%8-&Q zd!~O=@Tq^LIcMgGA@UHmn1B!NZ%g{d^G6ImVPjWC4t(|$$K7HtM^+t*L^0ayGm{@Q z$l$9gX7W0uEbZRNnA|RXV3JwOIrl>qXX>#IKr(tW_^~=M@lZhkqa+m){{a#2()zp0qAG}Qj2k}!1 z1SL9LP4NiK)0&=7J=qU>1h5a3c(soX_Y@TK6Bk(JT7u58=id=b8Wsp>&N>7F6CM~; zT=}K4;T&M!CY}E7Bx(%%yXS$8$H>-HT$ze$5(Aefh}5jzJyg2Y&CxCno^9x>7>r+f zI!k@I&jmB*2)c8_Nh|>HwTz|;W3T-0SIF7f+>vD>$R`~`&l*q`lz9~KTb#7U3F~H%UygMy8rIk9dYdD=?}+tGZgIyfUaw!FS<~*pYIl( z`c9o#wjC~>1{0{COLqz-fshprI)TSo`rxUM1U}QIAoF|>pWxA+N!kyt8k}+@IYpZB zERjiL|+EgQgJy=SnAdle|C zF~=rGU>J*%x5#rOB9IFyG2Dhcj|AB%X@!5~FOYH1>b)_3A||F5H_k%?^$1{JB$U{4 zG#a+|Ym3c_=mf(8q*_~Tqcyv$L5HCR&Ea+Mr>vw{f!jT@y40fj_+$(stAZM{}rFH zH>6w`B7ObkxA#?b8cm(4aPVyTYFq7n0R_Or1ws#}@&6ZmGk3q@6XYE$$B=M@!VfoH zc2TrHE=69(X?4UWa_V7@f+zBR2`aeEx#(XIVRv^NAb6pnm(O!6BKv*W>H^U_yyS3t zrvSXnVE(*a;jF0gddFp4&?P~TX$s!Y#`@Qr*|w&|<2sB|0AO?O5mwf?W55yJ_e)D$ ziBu(Q?)T0tv4%1fg+2Yv-sBJh#UNk3Cy;J%jPc;t%cm=^#p`>>pLs)%y8(%mj~m{& z9oII!l8vErAty!QeY)#t6q!8wh`=7=oPKB<8g}oJ^xB}q=3v}yskK$%Qm+$y+H-YfH$c4$Kq>!~8xNM2QV1SyViuUAY zOvPj%v0+%Z#wA;b!Is+7c!l%aCkI%AD!Cm3Q3NSwAAj;4SSLkAi*+VrEic*GsIL6L z7)vqtcCtljlYzR@I5MIAO6i|@j)dL;i@iHDGYVP#dd%D~TJ2eqfF z^hd)x3ft*i1Uw4?sw>$K1X;hmv*%PKqTDyBdETs!7)%u{IPghC_LjCYoQR_1EDCs` znS?3o1J09}4>NFUGV0D`!Cu*$hpu-cGzk_MpLVxN0Pz=U&2g}M{7Cej>@%e!6Pr=U zTz^lBzP?Iu`{*gwv}Fw;D9!Vmci+ zP6pZExEH2IX;Cud2B%COK$m+M1UA&P{`vJjw-;mx=sbL{f^h-)_9`&T*tpR z6auhWlHs5DVhLLuq|?!87H1*vP{>r%zihMu;!8SjGAQPb_k4ZnpBRYnLhU=kJx{uQ(QA8>~$aTyF#*sr~m$3^}Hr|F`e{ z=$@aJY-(H1r<8Ux;qGyRgv3|&?uAN?g+QO|>0W|yZ-RPFRF4^FS-~0XsnAmbY^v_O zwO@)==p-!wK##y7cg)0>DhGw=wc=e1q*ZCpJnQRe82S}<r$! zUtiXIMA+6XKea4|Wl(Mx5I?fh?%#;UI0@mQBUSDm{^}yGLD*s`zn+Xo04<1i;$*Ai zUvp6yYEbB6bwXk0*^A7~D8w?Mnk1cn)&l>|JwWwkox>eucAdZOU6`Lbn=X!=j1DyY zaEg14V(ULn?g5FDNZ)8wQoWy?y`OKzuw~QC6-y$^;6_I|lW==`8p|OEzV+37$%Rps z96W6o5fuFMBo<0QT%z#_RSmk)0O*+$v`R1pU}W$^GB!ya(es_Odoa}@<4X&#o;=c z-kld3^Q1k@$5M7+O}Gb0#j{h$!Vcm&S+B90XbkLr^X%K!ME><|W02`fepUe&pq!av zyP1Q0`_z$D^knS0v&nQj`rju%_AOJ5%F8=Xjl9?8~@3JnvQS#~iv84?!*_tE}mHx1v^Us|XS9!7x zTPUfI5x@>-HnSAZp9i_*h+#y3y}de!iiQfvt0q1G zw3MBpqVDyw!~m{^OvHW##UW))aBMETxda2$@zY9w&V6hh5x@b z_?3XYla{k(#f(^G)sFncXF!}7*v6sa^B^RH*C1@ewA2t9A|+4L?T-lhyWZEv9#0-XLgs3&Oe z)l{C&(b6!IBh=^3_8npaf%&?r4A)itkcc*lhJCNayzo+#KxWfvi{|}5w02|Z!28dj z%wo&v#aTLI`;#nc_NTK+HVYRo59@0Y3@Tg3U}pEdApl`}A5>H@dtxj7yjb^Ef8ByT zRWN_nBkoT~2^SUGKaNk`Wj`(wlB7IKb4TAvIDJEe{o>{kzz~dEL^N~%Nuu{+hppteDo?2+xREDBmGAOFbf)%lYmc>KM5_3?OV7m}`Y5AY=_ zAKwp#7_RsMzD#s-0xZS_P1`lP-Y*1~9|=6wS%AU;N043Vna0#55x3pqumLyDrF+Rt zqjEXD_{wcTA2De$z+jfqXg}<^&9t!op3e2504=3~*ZZrWV$Pyzx$Cv_FAfj}F*Lyo z<9?&pNl_`p?$|I$6R?@NK+j>!qfajXzoVWlNoFw?)gd8>uQ}cuZoN|WIjApw1kgAZ z{_;$6m&r-_)$~G-l-1Zso{NWrbi^;1t2|2)^1O#N=%LT1@!amL8i$!Wf=&g)nC_~p zd1hp4xjdG4l;r#62cV}}`RNz&@(q-^#Ev9E_oqrnoW0KPQNBT8RMzR4!fk=8?)10B zIC+jz&>SvB-#?3#%lFn~6J>nc>%oC>5m)WCod^Ml>G%**;??pnRyEy%*}!c&LF+ET z+l8YA01k6JoOL?CD@;<*c)OpMZ_G1aG&a$sW2HR;_{#81H-uh%*)&mzp?xL|x?jQv zNAm|l<4|9^7Dkpm+yg*s1>ASAb(1&V3cVH>wV<@+KpbED2qwe6eq}BtWEAYSWk9=} zzaafb(_2AdZRlDe9R2+A5y0og_M4=~lpPk6YEx74DInZSo}Q65ynQJ2MRXpTo<}{2a2ho5)@Dyba<5N8`5K101Q&1=$;YeEe z12?vZ7+(CPCa)YxjSv86so${;O>Ll3u4-37_9-ZF+cen36sNt${_9IGgS3q_2*BY- zZTD=D6vkdvB9?Z>5lg)i$`JZSe%cgLax*#oZwk7rFm>$6@QIcS5(hS-JNT8*3NRl5 zTz2&fYSl21ud|I$Na*p=%=eeW_xdP&MSUr3#EV|>=xT*Co;)Pe&Lun=#`C}lX7Y=` zpO<#|fi$>csJuz1y0<{LHRd3a)VFxQHMQMRXj)j(#SP13+-VrCka)TyJ>?chWB`C` zemR#5=e02Mm62*nv}~HBjnvMlss~T>Ti#%7(Pe&u0F+SR|0lkP4Neu+j#)R!?(EU9 z-RWLb$_K#R^Xu$~EK{(Nxv{elMogY)9ka^YI8f3aS@5Y-d<4+yw)qs>t~Qr2{IWw? z5U;qcR;9vaHvg!sG0gnpWtGo2=)Wivsey}Hs|Mpsp{EyWOj zz6>ITYGSLt<5)Gx{K>MhQxpHfpi7#h!uN|&OS-?r=9kC`_ zL4vE894W6K9Il7_5VlHYYV(&J*(6M#={s2+;goPk+LN(prWm`6u3|k?vYv)76Vmsgh-R% z`ybYmBW=GNdWH5Y8mRjN8C$wpm%xA8wQcVaV1^@xhZRCpd4Ht3e9Mo``uFEINQ-lj zo_#KO)*!o_b(gz})>D|niP$00_#@H|lp~)fK;xYC4oFA>%umoy_%}+QzZ~wgtdjZW zH{YQ=6;^%3&%j-kd`y22=LZKF^>eo&?`&AtO%H4{7a7_!<<7@PFuNxNdkUF z``z)6R@kCnaV?aM%clKtydX~z8rlF-c+6HZBYWA+F+I&jW9bqv5Cf2G)C&!51CW)V z5JBIbjsO7Vp#J7ti1-iK6uVaQI97<9B3fh`aO|p(unrWbfvR~SE39n}f)4^v%dHPx zttO&P&@g^l7G9gV`_k+zU{gc zCV{0JvH^1nB2{)l8;9!s3Y79!$zFb|HR5`*LH$I^OgUn`=o;mQ2oqRg|C2kLwk7gU z{QqZnmYCrsSTM*G{J*%f{IpEt0%D*oWinIyE`LQ-Ca;mkVUFk7)RHA`>aRW_Jpj}h zjP?nVER8x$eMY0oEiF@1jk87(hQ;9Gt1wD2N&u9mdixB_#FBjwe48)d5BJ2^Hhow- z8!K@|d{57iNRaep8~`xr?_EEgDZHedq4UJTFz0)N+nAe7>p+?CY4d}Hp5ps^1-7<_ z?kmYYo~0kX3%eMzSJV=#-x#QK3-*MRFr}aUn*zbUUCfL9T$5EbxFJyKW-T}MgiZ65pVr%YA2rHecqgvQTysOF{sGO|T zIPgtanBmL8+6o}MNvAvRPU%)+AMyw%j(w&R+(_!#%u`o|GjtuWq`%`dF z|7!$cdK>veP;^vxstbv0iY3kXnJd+zeCV*N7{z4lH;9VC>MCEos8Zj}*)We5Ks^vOT#LG_d<1aomt|tZ4VRm2?dvnW zsImHhbF@}(LbW@p%E5roqDI~i3<~XFVsq?V$0@^$Vo0A;kg>u;P+jhQnJA3c-6^tcbmZ?N?J^7O@nInBPA>NE zoU%q7-nnNS?Bwtg-)SxkO5Do=sAG881Q><*YVEKJ#rPHB)SQ3WKUZ7m|{5G1gL zf6Ic1#`OSDeBs><`{d}~SWfDh(-PFOp+D>#azBYQcJzIH=2ZyOL8PYT$KKXzu5Fmv z7%he|SG(pT#6NY$E*YwyY$0z>27vF^-!2ahDZpa}n9_kzX=Z1!`_*LE9N@2C5LV>X zNSC2GLKw8Bgh&rrVa^vd?X7xLJ*BLiZew>)8JP@0tsy0o!4KY^(ilthTy)JIw6Nzs zpS^0eZdJhJpx}oHnV4l4r`LSiw|bONW;g{WOhGtVD+6r@jDLKp0~x`ZaId0Pu^$mG z!YNuU|11>-;K<5ztA+w?9-wEq3~!EH^rC^IG>}OyvO1ZhY<5=f{!*=E&MD~?afe(` zQ^eCgdd`Ik6<`g1+I|+c2Y-az%`b2ESFBM0M)b!%o`E9*3VE|!G%6Mg1Yq`~L`e2` z3#0Q6ncpn~RA3znV^%~m(Yr!Q4wfL!9%KsaMBk5K@RHS__eOBK=*ajA2L^6GVlX3N z5`Hj)WV^JBtMmK0QAs+h|K?a1n>94es(>m~?~?}xF;puwY*!Ub>Yr(h3|r2t($B$$ zP~B?&@_y#EB$wWsF+BY{@!{)GQDRr>4G8GX08$co z*FKhWRBR;Hp)t$(O6&(|#6cm}J`E+yBH5~GKBRRc$c6gBR~YZd?H_c!X0i97$e+wlbWfl!v-|?+`av((nD-h9zTe;*3*fF=mT9@sP$;h^@e5kzY!z3heLsT% zuAfER-WV}VI#j$l&F#IE`lQAY5v!JL-!F~jqwlZdY5}gv#w;gTQL8EB;(m%FJNIP- z=^S>EPSiEk)2#2yFiRjDGlW6M`*ukXeq=^kri5Z|J3M7~>ZvR2vvO#sfQzjQ&_i7G zeW%6L{5eBkbLH*1gYQ?2X_Sn|S6?Y2ypnypPM=r4D~m5esYyHy&rZaW9y5{p$Q&8I zD+kGKzXym?f|F`8j0x3+7uD}cH^&yh(bmOM*mt$KXJQnDzljARefze%4M(Dz#Xzx+ zVO1|70MkMrA}$i|1kKy@l>Iyqm|mAn;TxTangaMybbXHhW0VC{p3<2AZ!YDv=w;Br~){f>boIM$>Ivl!_ZDkQ$nTmprXtUuO@EC zu5Rf+H|oG9UbRXdmwmjsMj5@Hb+5*gUIUyV(`00?(9t|Y98uQS`7H%b4a}@qB=~kD zJNl%;q1-f30Kj#`b^XqY;KmX!#PSvn7h0BR-68`e!a^0)wddow{t7q93 z23IHRd>l_;s!0SiwIoyxwn9o=^dXrI5RT)1q8ymEMahSxb_)Kzok@06v3;Da`})qx z;7Rs7l2+`@aF48Rb$^9S(BupAbDQ@^-!&2O4Zt&T&mf!GRmYrSGD^~-)~aJ(r~f=P zzXkPp%wX|dJ*o=t5s>2|VP;Uv(}R?oyyE?qap&{-=6HkU%ZLvzrE&U+=YL!$0|0~f z8RnH$2a&HAL`5<@mDB2(mrH;P>E4pp) zRI;5MLP`)j9$rhpU^%i59A2d5^3jM`50H`K%`{nm#31~OMviV8Y^=0T5pQ-Yqa92} z&zbmlBZ@$jdFK2mWQq>}l|Bvcom?kgow_|+Z3^V$!RsnBMl+$mN!vXSy!ZhPv;l%K zrGPSFSM-$Yo>WUjob~=oJsV;8<;Bz?Z$GzJ#T@_;=Ea;5E&MJPd$aj*%to>VOs2AJ zTKlYSd&8mz9k1Li4FurheGtbDjmAVb986L5=J&Hb zXjbsRdj?V6{bP(ii0H73mbI(s;1)`#fSzGjki^F*JkJ@PKyC(FSul?b>IqWRYqMKA-}uOBL>4f3{bW@ zyTz%HOgtnR-&$Gvd`{F9Jq+ol3D)Qht#5lUGGd(4pAiH@MIsLrSvU5}K6{_UScU7Y z^5Pt92wAE{0NaEy)Gf2MY0Ah8O)G`iW(;QZ;a#}?FohojO7=WYalC-{NI+y~_EDwi zV=sx`1pf4xFg!PT;~^i>%+&Wa&VRL!M9>Gqpc#YcKY&`|^?Pvk}rMu4*@jOuIl z2GA45k+^Q<3n$hEJ}H5%^mk!J4t+2CNiE<#fHinlyX(V^mrLr5zSrj18__zw<9gx~ z8-CdsA0=9(v<7+51N8jffKLM zot2qV{rO+T1BtMm&~J|bVn~;7eLcNmxZA~b0&ay&xp{Cu8`KF(>b|5<^7+OC3wE6E zRaC)9Zt2uaJ8p9nV+;FJ$hP=MZcE*0HJVRI`Wc~Cw>gkvKv?dNlxi$=$10PLIbWqn zy&(|y2j(+$!W~8T$<1qUtslMW*KcYqq=fD1?*lqsJnLJL`S=7;g`mooSvhk+>K`AA$k?mqGBAR)&5Z=Kbk!5;%g@ts;c*B*8)`W-m*+ zrI;s1@2l~ElzBRiYX%gH{gZ+ZJ{&g&&k8|ta=Pcr3!aJgXjfU}ya2*lqxV8nFT$)e zxxa)@WE6?g2&_fZU#H_g8O0ZYM|C~{BZPkSAW&!M-6>ADnh13^tqHc0`SlTN>>VYWLhzf=cgeyhbFOMX>5S`jdCYmq<_ z5{U8;mCEXcgv(e~=y+p<_w*q91iDrZQd)bcn+Ztxi2;P5DvO64mWE073;ANlDpt1` zkP{6N4vnszBaq5T^WZG}<=pmdgW5)~{G?4p_g;t~kI)OyQ80htxrhE;{{hW-aG~*6 z^Ckk?ppuNmewIc94$2=j2iQ{z?jy)G@BI=3Wop^;&<*@Zli~Up+RDb=k@#x;0Q>oD zUF>I@!J?IJ)?jhDNrFtL{7gfOLNQ!3Sp)##=$n^#qu*Hcv3`4 zd5}gU0p1Nvyq5;w2_6 zj1!S$fQWs7BLQ;O=~Z z_{H}CRU=##t1-0yIz6KwHo99UXJuBdemg!iRVDNKDPmAIu#W?Ia!caWzVmBc-|OPv zpzLo}s_IokrORSazfr?oNIL}>gfqKrk>NB#C%BQSK!WvoO8z_3v_rqP9AxjaokPD2 z2L{MDNMyHpn=~5#ADh*FO^)F%P8=tDUaynkH?EkI$lve)+hO!t1-Bgxq zBKzzBF0n6Rq?{!-l1$34GfORit=D|18aDpphq7Sr`5X=nJ`=*I<2ir-4qpzMT!9%? z^UuJf*A4R;53tRTPN|lJk&^FZk?ENe4Ptk>%hV*lWVqx^0n6lZAowDFg%&$~3EH`V z3VTL9Jx?4bekH-kHcUC01KSEL(|t19bEf#yorOU8W*ZSQ9vlqlMeU^mAvnuV9-Z}x z1#^s&AQIo|@7z_+A#FQT=(Dj<%54S0_NQXDT(*kH37M7};MnP>PD^FIF?^AR7Bwsd zy-8L9{ZD^HS@21px`Cpe^Q|<1C31M4J0Px@nwFrthLWG(?)b6Z&Oj8oz3HoEK3E{Ig?*uAM@59N2W=Cm@9nhI#0Vq&UZ(kuJ^ltDQ&`0fTnomsOk5M<*HUzLDx}Q< zZm>)ic=d~Mu5ZXU;Dj36<3?TdIRYewPy%&>R1$F)d{|q-GTkc^?AAv4tWXm_fxTK;5R-e|Vnb%R;KzJ+rsX^x-X5nW-U(DwyJ*}Cv5DM#(6Z~55mdP!X zwZ{NuQW`6;?r9|ZeXW=3EEGcTVut@==!bsZfjq45m+S90ndZIqrtuEz36#P z$ZMJu{)ZIv)Q!#h7KqvmJ0Ix9R6f%J zy6fXZ+8W%Ch=aqtL@4!fwQKpHHD&=1Z*gS|)FFcZA7aoMQ+!iGgsfXluox+!DQuJR z!xqkI@tm!Mc&4evM2#V5lY0iK=in&+=ofobQGd&f^FQ(Q6ZsNV>XvopDj1BrfZJ&g zWOgSRn0O%e3#KN^;9#)qbTSUD?!aO77m;`FH~mx_g$5XWWtAah50#g|uI^>a@~weL zuh|{ND}SNJ+;&@1`Qph91mJU%(x?9iVBFyeJG33Wzh=;Li@!QN&{1mTKvLqWBn-qm z1=~9WQuR{W+9LSD;8EDNDIfazcw31_01rYuoowvp8fl+qF1;cqza5?pu%`TtTP{2m zL(Wrpvj+aqJ;3Ktp0raKW$UWpc4!h<#NRnzpRyObFtQwC$n200ECm9m-o9{Wcc?r6 z^;fF3dO8c&#^m0{o$S zfJ8mzF~Do&wILWQLqldiZ3EUqsV=hly^(R489x2J{sv@gq=#>;8}`a~W>LLZA2C1B zaF8|E^B)`OMv!> zL8BgtGzFC9D-x*TS*;a=x=yR5hOBBv3;Wb*{`xOIk6!(g(IF@gua*A^kC}mFUhGqJR(h(ftlV_Dk>vl!N)W;zVwf57DuPuby8|+$( z_-DB67mq}?DhKzMwj`>{Y;P*F1O|s;wR3*`@a5MV2tX$U$h<-rOv^0Oinr?(vC6(? zKDD*)SZ*l-RJ7tnHF$wRMjVfY^>Nv~7M54d@_llc&88{m{1_lbCenx0MdD9i9dmOc zNuMHoZpb|LSBx-ACQ16xT_uDE{+)XUQQezbTbgReY(tu6)sXjpqDampB(aXlr1w3 zNZCxRUH*fehf=gA!RUJLNKrt3{WMO#4XVHa=0&?TT>)F`j$RGauG`9(<&V)I!E_zX z9it_!dIY2m20%q|Lu!bMKK$;;lQuglZkf{|JwVBc$C!|GsFIPwI_9&50KC;&|HqF_ zLYJOV8R~-eSO$-B=r7^#L#Q7^O8Bu4Az^l~ks-qwLz?dJdFQz1up72UE@~l(9`Xnv zgVQc8bw=8-{ytsro>IcOph7!Vaynd}s$x8=O!Djc(U>WBX( zzp(>qR3}9$#dB^QS5O8CU{H0H;`co3yR_d{ogfenJ1qcv3dFnuKd;%J^Ccy=WJGqJpw3+y*@X&Xz}!=q~!cAidP_A z^7sUwY3j-k9r}WiZLfoeHArH9Z17CIID-rHeGEEEGb)YqCaHdj-IdFR??uD0AdnIU zNB8G1E7lt{IA(^fFTY;x{Q0q}pQrJ4KN58?kam6lHvmxY?x?e%rexS6c25J@>{jUO zDjTt6V)+jevi(H#yUh*=K#6Dfg`!}7LU?Ai;wmxN2y+Q>`ifuM!8&gN;m609<>3Rb z^4dEwhf>s3vHCydrrZ)RV?6@cIMFsR_l2`}gB^7p2P3&Tw{qX8&@X>!N&MSN^SG7D z1A`?0KgPZ?tjexw8@7bfjYy}oGy+o6-Q6wSNOyzMU4jDAog$5dbazU3OMll!pIhEv zAN=Jy_HjU42a9_(K(5(sdOO5f71CuTuk4 z!OJ#fuiQFBQO$h5&HdqPx)oa@e6b%T3jl$~QS_)BJkb=|N;8o_%Q@ydAT0+y!!jLT z*^1{IfF*fPU?$0%dl7;OWcxXxTC~~HPlNkTVSpLaV>fPUCAd)Tx$}QtOro%`KpRrf zxk~vnX>n!Ee^PUPSZ^cIlH*o^^P-WL*)a4Sv&Npo;eg+(rOGpWQtrHgFuex}O0+M- zNt&_^NK0Z=1xeC{xzU{UF*K~oMGEW3FnX=Jz{Cs3MUEHm5S`c*`C7A1*Jmhw66-#$ zJd^aI_jLHWy_R(a5Ga(pAcfJjVZ6)zWSN$E$~$J1<_qt5)Hs9Lo9_12@rHW>K}P>x zBG{F%YfK#jo@IQG6- zE^}Zr$m2$OFj)N?>9KE|iV(CwxM^b+Qo7CG%UOH!9q83=N7uOz2Ea1@|BUol9G?_& z=yR5qzuEact~8v&`sEFknv`YGWr_fITfgD&7ge3{5vs} zSxFweY8Nq04E2r2TcGyaZerY%J@MDBKs_wu*=AvRPpm^|jxx^JOWw~6Zi>Lf3((e6 z|0QY8^d|JaY3HyGnz^ZDOmvi^Mm^3(u^K<|q^x@h2HxL3|G0^3h)&{cX7$l+f6^a4 z5*Vc~XD;6T-`m6pU3oK9vaYx2o}t}1U;UB2<6JzZw^Jnk=05@ zluWg%vy^oN?C-3527aa(Q~f(+l&(`}uIwbL#W_AjWQu6Aj2_41Sf`WJaT^*%!BXMT zgNQhu$1v4Hd@J6u4JT%+th1SdKKFigh-xD1ewv&CPXc}g$=r_WWtR^K_CFKwDJUgvE>x%8_YC4q-&@-*u6%VWT36-*095yA-+&I(D$#;tBTf;xKUpkLS^yksm z#$Ggh0RPS(2~yCUCYgU3#6wo$oY*=N;yEJ?G5xTdPhD@-A&lg z=~W+pet8N^Q~=|?mtcU3y}rB5rNXWtz z%JbHT0xoE0EtQNdv{Yh~0i$8kS_)kYO|G!13K)yTpGIja|J@ea^GtPkrw8U;&$0i& zhb@6mcPW+XlG;EI*Au~XpU527FBP8?DUQllzJ-lAjA6)^oTa z)k*Y)w1(BNI$mXbHh3*xEcGeIbjDkg9S=l%$5-%=K=Wvlkk0~p%2At>ir0IZw3_Bx zBn=$G{aobe;UDtV3qV9tr>&XX@o}Vc7@9nv2XLk>m+GoYLKS)U#&J29uXXDH30|JU zS_z72wvwhqvOKmyFl^PTFWKH( ziajAFGfC%{s-4@tXp8Oi&juB#@v!s|$CbUF1vE8}$D<9!uF6gEI$J1F-Ir6|s^5RhO8Y?SCer9C!ZO^K$evkk}` zmZ^5#O8SR|rb?w*L!!_k==TVVI;9&Y_{NG4MHKgPdN*0}h zrOcMj&b6sV8c1mO0f9&1%pGqIWC`&1;eMZp>DUZihLux)bXA|3Y~c`p((&MsA-&zn zfO<0`PuGh5Kc3z+n^lam-jtEpydg5~y0FJ|Xz;!s|sgXjpJi{cCT;>De!j@fKO z&&Iv(uOZ~mYN+m3r+ec@bR^LUJoCt(oWQ2|4@dQu>Le>RghGB9rF_Xw(N*QkQ}d+v zk{ytsu|6C^xAUf{X3Vv{w1AYDoHTNDeWhgrGt{nPh_o~Ly#&P~RR4odAkOtEV>6F1 zH8o;KT${~(!!`#E|$=VKr>HU7@2J7v2M=~0^?-!gOKJ&0G5}s^qR*W;0cqmS`ySxYrb2+9T zvCUIDh0#;%NR9X>LHcfC0r~TI8;bx^N~P?)s?dG84qdMas-ZTOuN+w~ErGXWmtGBb zAKRlyyr11qI+rjEBE_50BdM#;-vqV>wrc{d%ul{9rDCg6q zchODvJ|yOfS#_W%K!*^INqA2~!1j285<| z+GoWa0nZoPU*GyfaN9!6f@hW>DKfp0r9IPs^1E&=g|uQuw+1JD>o|YQrK`fpWwig# zLNT3t_4@<=lT2scHl{T{YxYNd;*l}485}d9TI1n@$d%(`rRA+rDjt?sKKnx;lD-g{u@ncbji8k0jPs4>E?#48uMyhZ zkSocIEmEf}FuJnGh&!#4q;|?S<>xGwKk3+;4esLi4ZG<@EX=FY`CR~kwT^pZt%3}; z=XITYzhT7yh}S2?VPl;UOHe_bRGqL-Zf~Dy%sQ^txTPzB%iy-@%2yK-<## zz#6^>&9u;D3y6*Rqp17JHAxc9-ju{TM*R;6WE!NwSqGStk3l(#Z|Q%Ln;w=loMM#Cka$=?M!I#Uy}0O!`9pNEU(F zn-og5@}}XGd>ahRpL;te8WjQp;PO$#i*iYkW4%R+U>(}15B|*%sv(i$|v*ZZJe*8bRWHh zAhJV(^!%eKE-;%C-WEb@4mRZD@} z+&dEX!A}I>hyJLjPK`#;2)XnsrG4L3q%ka> zirrL^_dD&eQL>*oSUvfaK+*|<^0ob<#*mmEPKf;V>!t&|oU0Z3{Gg~-q8G5Q{oeru z7S-(tDp*?zt{^9s1Q;^}1ycKc#luTM+rgbmrW*bFXAAAaBP4NTcXzF_=VRdJ5Y?d< z`n-yh8xUzK`knnpV5QWA2ITH13;%0LTV3m=R~`uSI1eOv0S_*OXnhy zLuX0{ClCHe@U=>xJ@DjH)Vp1v4%okYkp3z0fv)p>}iSd_bUdUdNbm_w=rAzCG-}5GB@&? ztA~VAYe@=_pzt2UA*v@g`cX>xW~)t20avU*Px_t!sg|HBPplQ1{XKzv*~Rxs8E`Br zsyt~j=*OFek{2qjoI^$|Wzq_iM(+uxeg%^N{vv`l+Fb*(=l5mE*GM6=L9WXeU)&xL z2#J+dl3MfoN}h*&naEfl;-%N>C~-MohB{=VD@lmj{T$i9PuB-SJCb5K&jo~!Ak4hi zm+d==7InTLaM-@Rv5rsrSqNO|Ul<8wW)bJ{jG!hxWr#CBUpA41BzkKkrqu_mbluvD z2SiBwhM6|4*qVsn@Xd8l?~{U@k!zXvQkBECI8RagUwdePDTE|a`9~pNyvfZ(7L8zJ zXmtaeygD^8s&~@3ktgrOv*bTh-W z6_Ta?{^qCQlP+dzutUY443r?RG#J2NPbLx~d|wn{^%HD2Fj9NFkiQ$aIJGyo6H@U= zOcme9BG96v4zJmtjNmL|)yEuE{q9|+eTf<{waNcZfua|{^yl<&um&Yh#fAJlMUD^I z*?`tczj;g75-k<2n6)UV8hKg=oF1Vpal2oS=GAw|G6b5N0*R10>Gwh(5f7^*u-uafQ1eRYgB$oxa z)V>47vVHF3r@V>CLj5ietxFh8lu(287#~B*b(UrQQ7^DIW#mFTpJ$1^!Fz3fmi%rf zwWqRjsX&lfm7&d-`*jYKuKa&oddEg%Msj*L#5)OP(txporHzB*`}W+_KNR^@4praw zEQkn3XL+qG0q zEj=0<$z@PZ8R{$m6FiswG!{VgKV|Z3SZg*X{6T`-^sCKk^YHtU=F(}?N}=_tlb9d9 zfIA;B_r1NedpK^#<7qvk@si=n;?dvET^yhIwS9c%bnSl?^TGshwjo^y=qDm$TxkQ8lxUgs5Bsus)q4tD!NP1`5I8n+4k)nCiNoJ;}J)HEr0t_5yN zYxmbYC2wdPx1TZoC_w9uD8k^v!+j*1hF)OumQ%H?Miv*{@gfbVhb~D6c{bq@24Czb-J)eD{NFDqeO-_CtFdn6_h;-6Bid*8t8u25q3iaS6y>0=%4R;O-O zk;5o3((B?+j0cRzFZJaQtz%{k=5@5vh5x3mU@z!dlXpKa4 z-~l2+qOqSsrKLL@`Mj|7&V|rQBj}uWim_x!=#1l|=f(jSK%S1c6y>D?;d9UQ!&QR5 z7BwzL-g(VYC4bW2Jvyy>wfDs{uCDU8U-ittxnB`!k4WV+=i)~pc+mKXM!997vu_zt zfwBK(c}HGL0w3=_9O{PrzUE3dPA<9GOXd?V5dnHYXE7MS6 zLJ(1-B(w^2lWh!tHfokTegh0b40L9h}yF)Y({ zIyrNE+${AgUqBfF%BUSV{4^jVgHr)`VD)6FwW*wOF;ij%({I?dnGKIoTn~jiW!*1y zM6KPbh{?b~SH>PyND_zjKC1OS)JFM=KBpvlwB@rH!Dc_TdEADf5-^{*;62Mz`#ZDg z5bzZE|9uX zQvUowr&fo*fi@>OEQU=!A|>pazWhDDdJjoX;*o7E82b#uAE&uF=AMs))a~9&9rN`Y zv0&iWI-I#$wq;Si)1>2>_Cl6`hCz>+YA~d~Y^rxUO33IomZXsoN`wC%OI*RUoz3+v z4)=rV8{Cf=H<}7PZa_H(qB(lF_+VV@%+1ka zXAYAS>ffs>V8O|kf(-Hn7pW&ib~_Cg&K6^~Ld^dx*r!$1@l+$c)rTw$|8ra>p#{R2 zRag1gjLPZ(9*`-IXTmK$iJ@@$a5cpA;juP%e2OatT1^Rg?X2%VbVs$8|07YGX8 zA&K=)_$lk6(+?x#pl_&TU>9Vr(!4O`%9o`)W|t)g$a}#B>hT?z2=m%qs7;qA@?>0j z!S6v&U%u`UR^|&@Tm_S7Q|`9E>0*adqumnp8qJI3#J_4@DQ%(MjHB+WRmU#_Fy((B zQErwq7(wBE$ACRmnGK+6B3GMKI8bM{vmyB9Qk{wv3Pjvzd`a`vux{LM{n_QvN}BWT z$PA}XpZ_8;s3rViY7d^&IYPts={+7Y3}b<=*?zeNn&Zs+`%NjlQUt!B6cG=@`>o`6 zw>$)18k_^U6Jgpr#5T)h?Q>P16`QlUxzGer5}y79SFZknq@gbCy}d$WFchI`_`b|k zG*#FKio$ZOuc^&lx7hy+4lw@pJfb*V^O=c9IPID(by7^#StVp%yp@oW%Q?>G{2jQ# zFi97C5OxN;K^)l>mm=iL5vtZVGFz)9>h;<*9M^VMKQNc_BFZ9eqFV5~-DG`#NFLI> zO9&BV2{qTJ<;3(6_m2QaPJba;7R}D}bBaK&A}9-9yaCDK6kbUBd$AIGE4FvsltBUu zc6r&-v`gSe8PU9p4t(xNM#8gMgFL)B4@6tPuLnaIX9JLOpT}+9eF;+Et6*fMr}_QC zNx)}ySmzriMlSD?Gu8h6y+nN23y1z9Rcxyi*dCq}LTyTxKN;lmvYAOQGod=K?H5)L!Ke!f<2b|Rv8hS2f-P2GC_j_)c**zb+FdcST=l9f_;@)WOW;E`Tt_-3y5cBipC*OJ$rRcRbx5fu@``8 zPb2mVT&G&k<;M>+JZAk;Q%@;aP_3{tw`YTheKo6k!J-45|GA&udte`+1y>kP*Tlg; zbAJ4Jj2lu(Wi9ZQ(9wJU-UYtDox38fZ`$Ry*VDKwH+BRn%+nh@xPik!z{4~ibCTp4 z=uXyGVEIbypD#7-t5KiD2xJ={CtKqp{HF-+VJFYUK2vF(%0t;9(a|iTl%j~s;`%C!zg8}3uBu^y_KX;6+WVv%$1=Z3zE{fQv~baP6ZF28aw zwTvuP|J9WFLPk&JrS@|%r8N|9ge5N*3zkPh+IsCR^wu+$RjTV#__Xb5fKw^iiCeaa z0FG*|+b^>>Z7%^x|I-6ydje(>80$*TWXDadUp^wgJ9yny7CEn$FpZf}U2{Q179xBi z=y(L(H>LG)0j0Vs+_rG!+NCA`a54^o8js zNiCKn6918z<2D4YZ2F5S?;MtJi6?@H$>EeX0ZU9MakkDYdKt<)l6l^A+ad;d26_R5 z-9uuIEwQtQqZ|zLi(Vj%B#ZB^WoVq`(`bv8Hqj$7WyI}JkvZutxU8#7lD88@e>A~L z^(t#(J8TuC<>(<@d-0#>uoGwdR7MDfXsX!nWawrfjLq40NEBM+{c(d>-%6tZ?;4Uw zPBX!g3_sqtU=5-Z)b;OoRP>r&1yP>79&;r#`f2t^NYqfB(jpwC_G{6mIX|j|bh1Sj z7T;@l1}uADq`pNB_^tOJB+QOdebw>x%dxmNm8g>=j%~i+I#~;cjcf<$0DogIBM_0^ zCG_b|2I3K?ENT4lNM19Far;$ie(kfcw;3By1KTK%gao4dQ~i2x!OYFWz0vlF`C{5# zn|Dsn?i6uEjDZ+B@Sl(F-@Cv#QtQ=+XD|@sN_%=q%M(EGFM|O;Gp%+62lu_u^|Qw= zpEFsKa}KpKh)|3nwH3#w(5edB2>7Kx=Ax9ojk`1b9Y@2W{_dwIJ82JIK|VL){w(Ck zI)|7L3s9M+_bcpWd^Im``>A_%Bo%S1cam%a7e6f~G%3qD{v`d~JN>R;(Fp=-|i;p?xNyq~xtsSDjc zG)EIT?N7l@_zxu6wdn+OC#+5WYEA9vBeL0E&Avh^D3%ZY7S-*V8-_p-tp}lqVL3dC z2K)ZZ(<}!%f=V5uS9RHn4T7AYA?ckF@Id(}Nb+fNZl5?>=-KTDlX)fac=*zo>YEg^ zdky>@A`P!j@!?_r2Z<3<7vW_B!Om{dEi&R)9cR9gM?yj{d2X5FQbAj$MIyhBJJw*zK}8Fz ziT6VTSPbRzCR_-7{rPLa9fMDFAep5`tumEf42qKmV(CVQ@N^4qiPe0gfX=c4(z`52 z)KGW9Q-05}Ez{BQvF+Ayn|jsUl_TE!ok>?DzyK9#k7AQlz?A|_3@BCj6FY3Vd90+E z(3j`=@RWK3_GUSz@D5@5|4Gh$EuXXJ$r}T#WL@%LiVq)fPhhmKrYGNVE0rEq<>0*n z9wxOp3UAMmO?}~~VoTg5KymHt?#Y2z9p)ZJ7^i1kUmgkRBBTFVb%9sBzk}g&r~i^% z2`tCtY@wD{ZJIS4*F{1!6>$Ig%cjio+F2S6UX1l+>%TQNmIWSi;^G$bs zpp%r49v#rH)8Z56{1Z+enQc`Sl#vzZ$lJjkY20D5KIT%AO;axUNJ!$s;q@JXMpUKV zL&`d{0ZhT1$LEP}4V;HjCi4W3G#bDG!Cy9=zP;dKE!F(ySSa``r-$}Xj6dFIl{F`( zZ`xXMU8^`6FuJ_*@#Qk)C6LQgv04mHfz~a+FBwmxeEU(v1x56s=8>3S!X{jK8HC1r zd1>PjJqJ<>+#q@dE>UY0;W1EM_Ab}>|6`NAA!ADRFjU!x8~meWqdOjzu~<$-v+!}c z?bSNX+}(2^8%E#W6_KURMXO^r;CVs$V=agQem<8eap-pVXRz5ExEt^>N0ZcVNM7PL zZKB~)m^mDqOXtIyt`>XEuffsi5b-mY!^a2!pZ-WrMn=9`K)~7Li=O>cvQsHODx%7M zif*Ah%x~^QFE?0rfLMHI3+ZGgl0FvY{bw~lTs93}xKr$ExitLcS!R27G=fJ$B4b!f zF@4ow^pPI>T{IQajk@~INodn%`thWt7j{n0RpYzS>TZ9N9;S-qfmKLP%rrNDpAGuUbk@MrZ%ms5 zj5QL;jHpU^CDTKkHisEYV|w`qJP{7lK#vXVS(%d!tKx}l95Tx`#g^=$US-}{xu5{8 z9jr=Ql0M*r#bqnWewb=-lQ|ydnT%>Ski;ch>|?jpjIUa*O!)e#@A zjtS*(zNR>7l$@PWH`d|iHuZuTgT(u0!oN(fzsezcS?YaCxo<-JhIlxaRSk(K18V3u zEJJ?(9&scv<5QmhC@k4E^Ic@t^3~_B_OoB}WrW8MKkm!Fs6f3mMh4~!J_^$WWdXLm z^1jeWl=E76cGb^!Xe(=jIC8{z0q;g*GIWr^A;Dj_>3>lMk9+r#xrs%EKaF;8Y_8lY z%IZDcw`V<=jCJ-0KyNAOB~=MDh0blH$+_rNxZ0=NQPU?Wzl$~+$&)CWU8f!iNio6s z`RgE|LBE{g>F~0-z!iE>tL@_Da)lac}^kwojRhnw4 z8rpT5b~{t419_1kqE|nWPXsZB;S>F*(~W^{o6P~PiNH6els=U1znz<0C?5%_^XAi8 znojPUlJ@+V;ut|%$T?z3;!TKp$I@%)K1YoEl@WiqM6IgQlPK3I4zm2sU8xeSW-tyJ z&3Rz2m8ro_hxwi=04S2}2oMxeQCbbe73T^;c7iGMB2bs~IIm;KNqCuqiU)o(e2g+_ zezk*{x^k7|^_A#4c(49zU$_e+A3J4&yvsvQBoGmlaNO0zH=C;U>ucs^(?(`o;Smy>;YKLt?heu}`BdN~ z;!&8)V3)=m&n?lSeBrjg8pd027*;H_+BI-S3`s^Pm^i)qCpm>?P96f;5`n9*Sp~a3 zk9^8scyBirmkEb2T7ANKNrACVOi!>ncQo4&@O7JBbD~85N(*&DX*E|NnM%gC_5w1sW=u@r)8vojSb9{#PYYTS{0a>w z-u_a&>RR#(;YAF=?AFN2_uqg8#uLz>$H-Wt1{KrCI>8MQVrR!*&=ZQy*umpz1qtxK z^wWAIBpaQrIqnXj-bhjM9Ho9Kry<(bu_=8X)9iLg; zs&tquMa;(@O*DMLOj$-m2PE&Wk8?QZ^8?uxO;Xbi?|vOA(UbU-IR1k~R%`i|ePbF+ z1`?H}{ch}w>p5)^8XW#vGG9M3?z-gyFmPrhN9U^TUAD~|T8A0t&civC#vui(bllAH zvyeD9asEegX^s`Mv&^`MLzk^_Fge&15!2{|-z9MJiSi2;rAx zwOSAh*V{6!Q!O#$Z>Rl3wgU1&KAS6}Et3`?Lj8V7Hy%mq^TtmxvtyzeEr4f6T{hBW z5T&8Ie%1-z2=EwXT1`E+oHLKHgYpR5!sr+zy*aH#d{wcIuc<18TRzEtzfa^Zo2KG} z1|`IZ&qtU`pXaEgPl$%gt7*wo#)Ksgm*WY0FaVb^m*j~4)cO3(?fs-+AA+RsWHr0U zC?9%=Yt3`v46LdjiOHiKt&1WyafRMy@M(5&L#wNC*Wz@=vC2y8m*xZf=o~Pc{$iR{ zg%SStWf{XOX0{h2|9qf(R`?ByxyV-=r2b46OL21GG0M$9=6X1%xnqE{t^BvOZOE8= z3;~RrY%nFwP&He~@*^=dLa~%!!-mW4N@w6ubMDPKw7IwJqIq-E3@*l~U>1J=Pjp0l zan@4u2xV@TgR*2TuT7q~wDB@1TCd@Gq-9fw%W`yph{Wy&hkg!4A88BbazRVrN)Df8 ztX|AG3LVB+qT92^{d^>(Vk|88Fl$%enfbiFraqxn?jK4`D7ac4~u`z%TLdK>7LR_ewwzB^`2V&Y?=e*W!f;nw3$ z{=)&HW;ddHz6W8#!6PAoCV{Iv_z#@z98*kC$_q|waUj%pTHh4)9xk*1x1!3F+{dcm zFHm)JB+OLh6%=R{D)kf*LXT6vUHuDH5t+Lre1+RG9lYQo5kp>4cw&sU3rK{ylZ2qD z9fh3j!2ENYbl3sfknAhT>|W;Q+>2U?Lh7K2Xt78LL`6uBqB?UXpgKMu(#AfjwS5h{ z*|h6Z<$wTanZ)_a3+x}L+9lT!mN!5w?zHMw$W*b~WRa6Ty6I8OZ3h*p|FnBsA?hNV z5^;#Y101S4BQa9$l?_cHMNupsYr8+MOrQ1$)4U~aRh})7!Vh>5@Sss-9aSt}Fw)2; zvTdMcuFueBrA#e-`T2<`zWhTbmnkp=m3%otEJuQDnB{`hxAxa5-)IrY0eWzRkQ`PppXcJ%0{ns1AdE_k*2{|HCi^g)+)`OS-(d*uCKtv2(hHf0%+GUg&9~hX+;(agNm=mSgkVf-UB607Q`(PFB~(F|(cX7H{)QoI+gg|isc^#Q z2Och9wfz(Em}D!>jH}-M#%VBJc1mkqh(N>h|BNt9@IszPojhV0h*jT{fUvA82;b0= z>gV!8@EVm9^&82sh1a3EM#Jd05L^O~*cGk=^O~2X#IM>3VLi{Y;|`&1K(^eH4kh+j zzLd#|U`S7%e6vf7|9WVi#xg@eg6KIn??`13uAyi}%=*(sNjdzVg0sIPg5I4AJDFPt zl{c_-QP4Xb2~2Da?ib|hMOwZ`MF8fzoRt;I`lfTENJLV`%HT?b@Ijk~$Q+GUc@K5W z5DObVQk#c=wBB@0Jw8l8n= zJPFSMFu9k$=Iz0bG2i>qVdIr6_x43D1XL9QS*fizX6`Xz%l#{AruqI!h5_I2^(h>V9EW}rrt%XtfrbW=uEoYn~|u9r<9vsC%JGeYUG9qM1CT_%h{;mWk5RRDNJ?vjzu9;x{0p^y!|Tma zS`r)kB^m2fT;Y1MiL&^F<46q?-R9^YB&kf0QhJ5vRI4*w<$FX?hsfuL7P^zksl&NC zoPJ87UO*uaCYxo184dBr58R+jTmv0n!XH12kzVDc<@2ZwpL_t9T*~K_M-@mAx2dd+UH`%5?UYTlCo$OO98`o03cSbkyn3gp`kghL8C7S5DI zw>)MZK$5^nSkcK{t+(tFA$jq6juIl}T@VeuKx`itemee%CMqyOH-7My&q6Tl2OAXv z2c|n$g_NJEzYMem>YyP%EK|#30gw!l!o8jmzu@M&1hqde<@-We`eiIg5iR4B_M*4t zNp1@mQtEmLK_YVSR29}<6X4fG< z3O>OfQ;`D#Y!s!8=VC7FK~d7pI>pa*HB zR}C~E(CiRnYWRj{y|nQJ7lS z)(_Ex7db#hj)T8ub-HiucNF4-qxQda>LzY^h|+`Oi@%skqKlu0`);V^vXTa`h`EP+ z35HbeVCXMvfcvg)%>@lK1Y!pYE>q|-mmViQ84w9~3!^2kpXKqMHxB3Sw67PD1-Fwu zhELH*r=rcrmXoo@fr3wmw?$4A;p?K?nANE(n3lt3v<$&r5`X-rJ<_3kzJs3mNmFTU zpO&U7wQM*uy1nP=k4=+#I2f{H5Rs~Jnz}M*uh}?)X~3N;EtnuHo7iGWJGj1fm2Ilt z{Np1bO$gMqfA3tu71$Cp4lhq;ww9y0x-FqP^Ln+MeM3WQ1P(&~vT287e0krc^8

~6YlRAy2TZ_FGBOsCj$9(ft(0!qD{l1m+FQyaZqR*=Ycqm0I z2v(kpGqxd?@Qc>W%{|VH)*cXS!+=N7rB!?LHS#LqytGw7Y#?qu|7ot#8{AmWIER_< zKimj49*K#BZs^NJ-~k@mtrxGrAf37V$^j1=zG#&*aYD#opQa0VqQf82@qQByEBX;c#Q2#;mCAI|0p&=MSsvA7jHM%LZ$bu?ydOHic3e*^;>WRiFsk6;C*QZ?SGQPbb-oa zPv^#Qa<(@zo2n(64(*bIt=oNuSL(#LWLW0ipfrJpwLSBO+kR3_L; z8SP8NYl^A)tn`tP7B#qMQ(imk?mrXazjn>B4iq1eTX~&_^L_oS|N6&WJ~(CgN0}tl zU@_-ijMuW)=-EK@*|v`Do|75)hH>f@4${%i>0dxZ)ZO&U&yP0Es7fp8zv~TkcCo%B zeF696Ny!&sC94sd^G8Cey&@*7JK?~HI*=nem9brWy7y^+x?Gwy=Z+vi(U2mkHGY=FFjVwi6kuqQv<0$L~naE^$xpp#_~4g5E#VPmDC$( z=V*T1=``Ysx(8_-f-_-{IW0NnBvtzKuM=-Vz9W6cta(CDyv_ZVyT~eheLBOJ)_>ta z{JbZ0cBe^c7*m0XTZ&B4HM4ud@#8As6ULWY=~XBf$7*1c3w}zhlw-n+CXO`gQ}enf z6(3%7TL=gD4f!McJ)ffh>VzMK3B+|jwdv2Td_PlxI&M-(qjTxDLgE8!K|idF9&qPw z-3FJ5LI72Uf=-K=b|1yXCQp za}kj^QpO)N)~mM}V+t-&`rpx8|0XKJR{HWKS+-G3=IgOVikXY#t|{2F^+HZY%jQNW zU_rb-mClxn3U%C>%zvHI?h%!)ZS-2B(-+vtss~03l=L>HvN<)IAdsF zOn)>J;#@v2U@5{Eni5!{;T2k-t4~i}6)vh2x1mDsI>ZaKXuQKG5&an{t6$>O$q!3m zGz~g)*)nTT{*3fev}UbXoA{BC5To^IpgBxPpSo9?sdKJ0GgmeiHfgkKiXF0Y@7)^S zZ#DV@$q6}zq4)Kr3lCeZ{+sDx{6yM%s8mbg7=FLo?G=vSV;~|Y%TAJy1mmRGM)Q5# zz1kE5Mjt#a83j^!+b?bNM%6EeQ9I2DwV;FNWDyPhiefZ;Tq=EXbWJQvGd-Mb^vBPr2o{Y5 z%^O7SSIPeMnWynwdz!}fmZ`%k^7fOy984t zhz1A7du%J6hc713jQO;bRp-a|kj|SQiODaFE%rl){vdrAcG)LOwkE%&FK@2}o^+@! zpK0wyJHz=yjTr}2}#Z^ zm^Xyz*ugdjD<)(LMZ)-@t=rh><;VUdBiEwpB#JnV1z0q`bR&lQvgSVuZsB!evrU>} z$R(_V*2ptIb;@YCP*OK?gidpC>j*h>1tOv%c8-+{?d|mul7VfUB+AMfty1c=hc@7f z&U%l?-wv=eP@vpG9BmX zNiHCV#-+0>w*SScN1GhjvUB_~RCXCGxxdH*I}O;+HjhhQQ(>$ z=fW>V$f+=(kXCsv_aN3fjY|@*vGP8~h73its+3n7U|gb^``n^?F#8d# zvAI9bvdjc#Z=JQBBVl)v?UCfIzPy$GAzm9{_LfXzY=_c!C3Q0~9e}i4A9C=<8aq_6 zY8T@(t!`pn3$}QWl8>4}oRY&cuP$M*GT9*%RAf7%H)V6~ci<*J*)hFjlAXHtk9fvx z8tP-=bofBQ!-i)+VU0L++)RiJOoqRg8Hcw&G@xvGPs(cVOx72m3si#WMai$shX4Bd z3lUadsjcwHhq;?S?%BmE8@H~W2Pyb9Qj{;8lVXVa z&Z*m=jRdMrirZ%&qbE%A7BDSzsF<@^fd^8WbKLEZfv<$VQd#NSTjOC9wz7<&e%QbW ziywF?kHh&$n*-UwgTv!LejM`YYP=QFR}%xNC4vRysF-%q&1b-PBrDhik3UYF00$P5x!q2_!N@>}ZwL!l72&ROIxZyzu4oC#jG0^*9!MUj6MA zxMKA`bE!#3Oxog5LP^Do|WU92u8t=(%s4}1F!W0nNpa8lEH|rTnz6q;cDy|hk zTnzS4;c3$20n&Uls5h`~i#skQaMt@`dIs{5gc6E(+1Ejk9aP?XjP6$T8JX zJ{~N1^zvg6WkB-ELg!A_@)(|pFF3ir=c8uSex=awAIz6@pEi* z0HfdXFZ%2kx5hUt`J!wETo}PcJ6%A-k8PuR42&g7KT8?AWxO47g9@z_L!Y2?sdl9& z8Ljx-bvh4!XcIMTudbrKTz4c z!9(^;2s(0pLTz^g!}3M$dQ)1sJjy@?wDohH?-X}Ng=!xT1fR;Xer$ki_sN#*b~SGg z=pWG%1wYn2MxB-s53jUy#&1{BpOdtWphnfq_~g&2VHM=PO`?!gj$#F4`g5v~6(|{> zr5kd>c=fZiSN(RaE*GO28FArtDjB&>r%3DvR>iLCbv$WtyMCT9~=ptRV8*t!)lf3?otI@)Vrd)lLUU(3xWC*V;dt5f$;MP zYXv+0UW=+#ep$S6aFyv}Ao&~fxnZx=1`P{s&FKhQ@a%Lgwsf0zM_NtNxJbWBcmRpq z-I7tsvtbpgk(l=t0eAl|>=N!(94MWptY5Z1ZKFVJVMYp%C^bpC85w~}??)EojDW@` z`mKl^#)^L|>?LzS+9M$?FpSYdSfk&upEbV=SCZp(HVi=-%lMQTx!C~u0Oy2@?MkCCx+mx z<@UIY8Z=ijjBJ5L_()94wBy8wP&*1mc(cMKP_M&+2bQ@p`-ndZxXvuD+Og|Bz!Y!d z^QxRVOq%WUk}Du4OFD{m!EReI|-`+=?D5bn`Pn4Ua+h*zjg?1^b3VF6nKMAc^~#c?pqt znrb&=+Hg~oIk>^U*`^Y(ztJB$_?VtrXh-|ikd-zws)vH1-t8Zx zx2&JFb0isveWG=_4g?jhzDdM`os!b@GCuvxyM-^?1T1_L!fW56!d zGHv7bk1~xU+ehqO$#>IwUdz0FYsy@~D%5JH@0e`_XS*6DDg~TT9LI&erE~XpF+*x}Q1yqBbbDZm1ej z?U08w74#2NnKLPQ0Z|az?W|-xPa%JQq4La>Nf}1CzQXIAnN(yOa1xg<_r+W>y>l?p zFFq1ASs&=#$tJn?i)w5m*E&Y2SppSN&tjSmW!hRT#VzURO>PMP86r*Z;EM(E?(Hw; ztw*KBFr%=)Q^zxIg&jDJUh*?IZ7gF$i1E;9Y`Zqx6)MoP+eQAPX)BO!?w+RS#n)RJ z8(~h0XYRY^yN*D!{3l9!|MW&YP2C zJhBJvCt;kN8bj$9>+*?yY7JlO?7{7sNTl0JCNBu)s~ZLQmSVdv%B%A*ETCkWSvFq@ zH_TOy{)6cS;3`6yY_K~sel zo{RE-W^T#QZXF@jl{jbg6qcH*Cc~{A_imw!A{)0pNnq{}MF$9rVdnKwjh~Q2(4Fb7 z?d(?Zu&PCHY`KRaM%a^t()t8Elj%lb%jYUTsC*JLeozW_tw;%VpxD%9n^&4bZuKkw zNWfg$DKFlc z7=q)aE;Z9rLg+(bc1Of;)GP5!r1&~Ndl!atO>#d8QIZ!PQY1nuwKPj}mK9gwmz4VY zEq{^hSHBX$tsO&}FE=t)s3gx(AYbBaqGMB&i`FLM1_$IHskM9JWne$#l{6J79CQF|6#($S_O2t>(6AE{q#rjjEC(Eb4CZOq(`XFAv<&o>janl}3 z5S`c-3I}tF+K+p4y-U?Ogp&eLX!|+l@?w$^zE=15a(bo_a@OMNB-Pj1E#;iTZ<=~? zxUh(Oga0t4P1%vLRG_eMb*Cqk+q()QUBb`M4Ssj`{GfK7jIomfPJ+sIUgol7|5^{) zfyRZIqAERy_L{Oat$_K3dn2J-Gf;;3ET-iTyDwW0alRQejxm4pks)7^%YNmSj(N^e z`^z+{vVY<4)G06Jd6TpT`Dt^)`l44!!Gpis6~_sXGj$$ry=zY9+5i-smctCTNxUob zOgPI64o{Bdps?6yBK0a$Kx+Enbt6L$Ayx27)IHAQ-~=XVyA1D( zM_0MrZU04bttG6MgIoSAIoT|6dr7r-!Tx*Y0!{vKJb+w}9*o@!XhI+Fe0V8}B3#0Ki<7qs`6V*v_IMgITivAci@>wu)+bEb5yrk9zr4&G~xt~P`Xp9qS|&8Ifr|_ zOBO4pF}ffqMqFYAclm_uzl`{?bcpEsqYZt-uE(~K z0FR4@>XSDG{{JkTs%n39LiyDGo z2sjCvP(0KK#E@(?_IMPvp&E=ozWGFR3F5@pS9^!tY;-Qb2qU|HPwQ>F+F=r?;~1~n z{TeWZTQl2?LX94Cn&)hr9|ZYia-Et7;C!b z5T0yHaj)i>Opw%m+>qCHdY?1E#DTS@igx}T;VxRuY+9Dm82VN}jEz@!yW}0*$pHM& zQ|`v{1@Zc^s2;*cl9tn=$LPL^(qp>js$Cw@Qv6Q$t5mTiNeO*!R6sh# zjMPrzcNO`b)8x2C$Mmd;NHG&ZUO*AJM<(YJ2Xr56J{NE3UT(Le@7$b$50smeYNlgL z8bJU^nFYbhUx)WdHtfg(=PD6HwCOaHcG}UaKJo1|bG^ew1c?c~U1lPx^nvrI*iuHV zz9tt~NwDsmM8@hO*CIw~CI3?m@gF4EC7=u%@1 z4_S=?5g)~l#-G~SxWSlkhgC4I41?|B)W1(1g(H4>pWnl;1d`G>L&|j4WYYVojwa@0 zK_Zm{Fi1;N!ob}?$h2_!F6B=kr0DuMcYT&@2^FTa;u={cQ+90BnO)X&aJyR-x3Ih3 zC#3&oZvI_g{teF(o7YzEgsx9#b2;Q&|HsRj-RXyHf#I=AApWlo7m_2i`!;30;_S!Z z`a;#^ZRa;}`ymstLMk}YSb2VcNuOD~#pPqk45LaM7r}L{vXl)%`k57GI=4{Hm|4HY z1&GPhuJ-Jm+U=ni?jyyP?VhXU2&`SO#FzI&1zJvANoOq}ZTcf7-OU}Vn;x@}9Bmv1 zoW6jc4n#HC)LCN;*|aSQmEvEJfMnGZLpCqf$po**i52_v$Ueu)5(J_uI&*3Ui{k_s zZZQCfhVznaUbt_7POV?3>EnKuJTw`ad&cD##`a|9a`EZ6Af(m+_w)QYV+~BhSH^(z zB6P;+>IW?oW?0^|#UylGRO??PYAK7azQT`a{k1IGCFY5ZCpNJOiUc}+rR&|nZuAiH zKvS-=ZAEEU-;N1lXc&WB zz)11+A(St+RGfI5bkqVq2x+<5vlH`20pS&kwtop}iVBiUk>N?^rn3VHI@taGiK{U|p8IbaWa* z1ICA)<;{N#oo2^8KaP4zeP->s%kep~f%f$F7~@Y#vmxmVLLtu zo0+-l88*N~LccW-tpEPTDc@B!q!SC+?~K}sxl@x_TJ;pQK5h8I(3VP6{SFU(NsF>& z6R>Sy$_W}^b6J4s`pa=sqaCNgQpp=puY-Ch@q5@{irYdTcD7TH%niP5BG?Fi(=(|I zX%rW{iLr%UQ{p`N;U)Y|4j%H7OX#l}z-S^)CNhVm{S*-N9wKIQ$9Gba-F!>z*?eb+ zzl1#z17vjO1*8?EdfCn5&(Uf6#KpPt+LibzFH-(cwC5czZLfPKQZjP7Ve@a%2iQ1t zoDAt}!xO~gS+)Rom|LUq*;~_vFCZ!Xw}4ZU!Bn07iGtYPE+&SEh!I0Aet;!3)f47D z;!C#+W}gFYEH5XF--@&{zDwsxKK;O-+UPa*8%Csj#wqbvpMlr3^fQ?XWI7;AVe8Dg zUyvk9qz+CJ3VxPCuDw$ zu|rgPa*@KQ5Kt&!r~Ex2Uiu_X;zQb@&G1g7z(st-IsOWNtF0CHYc&05GI_H5YPU*k z3#8HDwDjlWp75-v*yaSD`j)Zzw5!U7{`zO?Qo~#B;o>OS&Q}Zc&2<%j`0{od=#geP zU|o+*B<7R$06uzWtq**Jyq$E}c^Bdyb(9^;hAtApopr%!6#Wn+$8Vm=BrmtgpX*|M z2G(*!$WR&Gs#_OIL{TR4d&1XI7*XQ#@E@kohFb&7-zolHTN%aGXqy{zc?b1nX7^-q zhkQEW8I!<%poLatAcP2Ux`-b~+8gGuY`;#-{BGMjW0X|}88(tE{+Uc=SC+M4S(v2l zb$cXLL!4bRRAeE+NGB@U*>TWHU)az8VG6nnd!OIYy7)p**n?EW6I|B2RGLoej8!|& z%$>lD9q@?rt8M0b_m@Xpj|U?LL!*%oplI5rc;wpeQ^p}*;|K!1t)FEH!110+jQ$uh zbjBzrH0NUO?H*Sb&h8l))pyD{z#9*Rq98I*z6>3eUBr&M0Mz*pdry+3kT|bjT%!NW zmx0YD+fV;U9OX5!sGpbBO8{|VN`M=l#As_lu@ikR8U)@8DCd7mxWaFX!33ga@vX27 z?b6XS8+i4HTl6w2T!^h4lH@jLV7=hM*l#+CK{WCv)l*6CS*Hg!;|yM=9yguKur^Iw zI|02qA#l-pnjzp|pyy}#eewiF)S22y+O7Fg!di>QKG>lPT!AcI-#iF6B z7V3VEwwC_=i&%Vw!d7mbO{8wAWq|3=5=JyEtWSywa-3K5p7MB;xNsDO%alEITXi&6 zJSk4`5vXmpSMJq#%iO0-h&3Uw#)pJN7W4tnk`SHAXq#%>Ea#2+Ci|D>S|odRZJM2%F)F;i|ho6{CT-d7sH_$43vgb+ZVuY_(w|gFYjmi0;&;S$=$Zu8V;jKz!#(U z!oT>Qm-6r+mDXwrPNE&tm~z1lyOjmKpIE?kO66_PMA&c^6dq(F8(bCruI8CYJ#%?= z;36-bD0eutHASPc0w`J3U#)OX^$d$F_7F8Bfu!`GG9CO_;FqBMkYuT3?!MFiQ#$I~g#L^|*C z7?LptJ2_ozMw~+<9Ii!ZjhTtF2m8W`Pr@G$l{NBLOuKoVi>aep6_u1X36~Rc8Tjw< z??%G8Q4vInT(^kYJ%Anufe11URJ1h8h62TeHXMa55Yww2kf90TJL9j2D?7NJ$%II& zMyYFZUxBrqQ!c;xjWzVfRic3ZHk9j<@Ng|I>2~3N3eNN6QhHwNSENw@t<}zpY6G!! zpO2qE@=D~V)}V%`sfz-q3lGO>uElLaTq0;#pX2ecc)Kl0lkWzZ6(VuYtdnOzmy%~G zIJlDCQ?2mRd5yIkJU_?CtWX24eho*6+*~oJCjvOHtz1BX%pB-Mr+n@lg@Ml?>pKC7 z4(@iM!JzpQcrpz1Ujg(Nuv5T?w4+7x?35a*?;E-O+5djmvSCa2&Y`=?WkbQ2%2{Anmf z21h?P5&qcscc&=X$WZA8Mi?fKQfB>vp6qOZ7WM0tHN;Uuf?aV7-;cW4R;cdnp@;dXSxkKclI^S-@E zC}$gq_x^R!e|?uXR>#HxlI4G5@;>E4Br{mRfCpY};Z?kA7V~=ve$;u(fyO5Lki8Wc z08S#o1K0JjXhEvs1$JD`z*VwE3qK=D^MF3m_%P&aX{R`@M1hI={`nd)rF9 zIa|Qaov42RoeuoBxPx&ZbLv@g;^RKK`tmK4(S~cM7@}!x`M?)Sn8&eDf6;ibI<}f* z6x62Wk2;a(d59n|Tjz4pj>>(G(z;_pp?w$m37N*}#)}`GX9^6Qge)FYxrN2|o9~iI z|L9NgF0%V_l5S^a2BMGNKS^nSfXelrGj+6`?C@$!nAr&nW>PU7o#iK4<5m|6mF)hJ zANb5tnN~nA9sU3DLZY`pKBIo3*Y#6pD^#bBY95lruy|r-PvMeB{2Dm0>NHh2YE8sa zKmQiXJvQWim$A?KaM-Th(vsR3ckoK=_SZ9+=D%jYcK;-N>>2VJ;aLGtm?? zERG+XnHP8V03iJ*CIK6SMT**tfL7i`f#Uhs@4TJS-ke)g6DY}92$O&14FbgEX4jEs zCCrB3t%QzO9kR0FZ6p|bxVmjrz4;k>*|+_fNZ?{%lAxb`GGt2^E5t3TR7ECk#%4wA zDp6`!M!?I4p2xRO*IVF3_uz+&&(=FKd!PAwdM!}Z+kHCa?cWm}dq1_+%{#NM%4qMTZoI39S4tPE{z~J zWZZY=bw@a~!nmekL3wBDPW-l`QBS#QQXSBQa7Oo?L6_Kk4EPG}JQ)Vu1zMR2RxtXd zAv~>UpppMUvi#Y85_5OjACL zNA80l&nVmci02y-1{6cr6A6d6NHhWV+<$_9kCWy*gR;BRp-{~ zD1S;Ne7K_{{3|AloQF@Pd*S2bOe#J-QjxN-Y|Slmtfa5fpT9fLl?{uj_bqWN zZ9P06cw^G+Q_%fClXBHHP^e~STC)}nS3@D0+f4i+r7)MSm@Qoq5Gq56 zc&lb*tF|PKcRac1uqd;Y;U#&$y1!cHc~L^9Ft;cF?G28F!4cZX_wYOSK9J@8k11ky zcUlS@Rh#glkJwZ)XM-)x(JYV$A860#%>IMw&~c#F{TjO>V{nyswdZnr zBU{^Z#K!LTx8WgB@1y5v6L;@~Zo@I7q|FOh4+-Y5a;~dvEe7RA8~?9V>N}hYQ-7yU zA^*3;=S?J8acwj5><^nfI845Q8a07tVVeK0ja$wHxFzXrDzY=7gv#s%AC?J z`ia*Lc{8QL6mBuj+j4-I7*{?$G+t1Z*hBcd%_o8AIEokfM1F|N5&uS^*@$<72egrc z=i-8X^^cho75qM&467cnT(6OChwDPxYOnzTTwI&i|CbStOFR zvZEXE?}-lc@Uv>^8eQzmmRhPdR~2u{2}@^|%iSiljiS**DHzYEhaUe}6+r#%h`#Ld z6o5~8;(s_bRPG$l*&xLx+6~2nh_1Sa1FEi-OUdGMyJ93*=3%CsS6;U{urJQ`lM9g+ z2F+^za+m-HxSvT+;ra`Rjo=tu!Y<;y1 zvii+i}UiNbHxhQr>nGuE{%|*ti z?OMh%eWaZ<&TtcfeF=;}#CRdXT`{HvC_YKj!g_5C*V3k5F%!fG!G`oQKTDKUe0VUx zL9$fIbL^Q&rgGj&7Z`5q1Mu1I!xb`k+q6#Q$B7nPYwak3m8}Q1vF&ztf%Q9q+7))Prb$d9u?s7 zKsJ^iH6`c)>N@wFnS)wE3L&(Zz!%0>y3F39Z|`UORZ+}O*_uG&LNhHN^-o%uvmDn* z5jl9yb!yvTvF46d-t@@F>ylcxz$|Ng#S2s5GHt+-!O;a3XW1nHISe**EQe%AXhiVa zfIT6F& zD@)C9or)q;706Q9QmEIQ_=Of5oJNEEPmpt`2dsnb3x8@cQ?ub$te| zEB}w8WwH3Z?pz+=kwpTr8cIBOLDbqAeQ<*Q`0N&%mBDe_gwd}MBGpKBSI-nv(pW@) z6k9pB9WFRtN!FI6a03i3bhaN#VS)o*nHJB}iT00WdUp*5%Vyb^j8;cfxrlQav4I`- z$YdvF09O*sM7jYBM9WdWQ{b^=z9v@Na3J_*>uoUcVf;a>qN>3Sw-$Q1s+{ndNLtem zCmF+4orO~!^F9mIW|+rDM)99tgS&_YxC|az=KnR8Ohmfm3?|!q(PdxOKlaKM4N-n$ zmn8aVTVnCPoLCC70tiTg@es=n4>zAmS_*%r37^C0MEfzeG_A$|Tlotb6eAEkfUs$m zWOyJFuza!X^3q_$LXxk9=L%{2PCc#sRZf5Q@GxlNQYd{Woz!sCZ)&l%jxWC-Y z)QNPz;-MxxUIvy{NaczVQagDV363)GGP|`4K6B=Ll(Y`j5zpkX!tSjPB+zNxbMMd$ z5XTy77sw*|Vjk*2NdH;OB;#aip-&2#X=L{^NxG+@2hMN1!pP_|4Y21PX1PDV04GuV zcGqm^CX*e>R8byl&yGC~6%c>&IG84(`cev8v2CdlHoZVOZ!sh zaP3-^<6Yzq8XS4?xBSEOTkr@5I~^}1JgL1>RmDwRG9`wa)>%@M{xynULVN$?;ge@Rnw47nY9`IK}uO`lX9g{l_zzg3QF6qO>GDpbR*nMI!D*3yD_! z-!hM}EIi&hvJ(ybn_P7Lu;t~IcN?rh9Y-cmP`gorW{zo|dRF-PQ?&g|;5u;cVN=;# zp6PXwW|t&P5ZgTP+?^{A;zl}#hmA8Ixbu#+c_x!Zhj;j#TF9V)0g;8z7kPG?1vd?$ zEbKf!-y*MJyU7`loc+!C3n6@t4O~t?H7J_e_lgQk^1+uKq+bIEnIc zfE2l1QcSN4hY&wEJ0og6cd<3CPnv&&pP!-a1>g?_7jiN~EJ}uf>}Eilago1oeR$P< z$NOk$ji`n{#dQi70JI!`r(b z0q+A!Ez`maD+M13$wgdUe5LePUfiHGN=Z)}&FJF3bQi4~GCHN-X#S5O{MT8j>i0MDOGG#MCXk}Jd zICtDHLO)ebKNf!d{wr4QAspE8SCd%kuR!Dnhb(DrI6ChK50^~pV>7`IW@Lo3QAX@8 zI%`zv0cp@J<2g*=pDt!9NXpkP6vMCc8_rzCw3gKK2B5BLVrlqf`HQ*!VT!)HPJI1H zDnu3`=P8eInEG}D(fhXYz+{)_i)084As^r<82zZfsu%$mX57<=49-2Qh|IUfn=7ed zu>bn=+XNg??DjcKDqI-yp*<1E216I{fw?=%DT<@ryPve6@Q^0((sELCKumw84x^*K zn|*ELO z10%b@{|b>~%xwReNJ@5C-(?eV<7hA@wHEOV)tEI6f)a%WpoR3%PfOdK{>@IvJ^qNT z-V!Yq$H06xa@-b08ED6+`zxK4vB>-Pr@~t!pcy<&_)N@6!PoX*il^TnH`qlfNsr$9S zC99LabM>XsRYVHW+AaH=W<_3LtmNuKm>hxVmK75y zTCs>2C1jY@U<(4B4WFYjtjEk?n6 zY1h4+cx%@W|E{wr4wX|zOgBzU&7w8sWQV9ikp~>*4Wp^%iu0SFPg&l7 zR2MbUc+K5MKl?_d`|iOlRK-bUsfhXTpnp zZYX}lB^crqc-M*XMR-RORQ2^7B*I8$@P)Oa&kD` zy&y6DL6RBSMqH3|cYCy`MW6aCeBv?U+`rxlOPg9Dfz~wSApzvyY<&p7sy#?pZLp`s zM+Q5KpoZTKv$$w9R3r}ZP|AAl%z;NdHFEp2BRq=QQnJ0F6?gD^|N81izSM`*80hu> zNa6(zA_H96Yz?pM^B5}%etFNNd~Mj8XspMc{huovzPu}a+oMi4*M1%U=C&2JOgF;e zqK$wC$}iEVKRNoxlauz(@Jy9r)U|~KD_0#uj%`rVh$|*^ar193>t_-cHL8YhGlBS6 z$Ez$}C@iW#QJTQF8^SdSxo`xriKAu|_H>__o0zD8zxuV zxr2Kwv0oDJvbHhhyTL*G0fE-R>Srn485Mg~#j0bMp4wGxAa~M0s`YUCx108wF=$-> z?FZfADVLUEN+EQF@S#~g@^W6Rj;>|I=rH3>IRxIpK`7uPPQN!zi){10)TU()ybkYT zc1%LGt+H|=T2G=(8bp9td?phePoazSK^aDOCBEkM!_{4I&uw^CD&+jhU0}A+N#M=r{Wjw z7DNp=TkNtMN_iM>>dBLMJrha1&(d*yRGir@Nwzhj{c~@|B+#Bz=T~%CxduC8j%^G` zOn;DQ>1j)Wmxaa?c*z1n@eFoFWE$L*d2lef(eaH_QGt$|Bq&KY^KabsHG8w>MvDyk zecaYQ%m$#kcyiDyEWLCs0Cw_c%^U;)JIjPHgc&i;) z52^pm+%?VAu{Okux_hk5ePShuxtIEexRRPI`pvnqjG5$Hp6h2 zXM)D0jWNRJSF>y@@T`epnt;P+GOacT$#bZfu~J_U{BBWY&}N|sfpt=nZ*5+T{d{*X z{Le*7qbU$8-O?a6QQ#LQIINs|^-kS%(E!z1H1X#ib=A0bp#NZgwB;>(a0UroOOKbF z>Ke+n@ig@itJ+Sf2E^@gPt!AznmFh=;U{aeU%48h-!y1EYMp{LOTWIGZb1aalVsqx>A&^IhTli?}*WpahsZoaw-v7Nwot8(*BE zr*2L>i~EK{XF)qvCjOk3De9R_M5;3B8Ac}Y{BPM3HHIa%`5qf{GASGGEbHJiN#>E) z|2d8S65I`}! z5S|sA8&)xUVYB?Rz4nQRz}@ay8a^Ig+)!*+TnW z_4+T8B2nZQ(O!PcY>Cf;HwCiVHiJCMmMC-OMWZk28WO~TSgTO7kQ6*>_5J88^+QVJ zN1a3{`D|OTqUHJ&R`SXup|8(G0!Q-&{pz3H06CHkeUF=3#_BN3Pn4)eVI|cf1vvdz z2g3aS7q?Si**^XjfrXKB{#Iyy;_SwOjQY?0fGZSdlPW}7HM8$xM($E8}yaU^pmCX$QPP63W(-Cq50>!Q5{+-aegE97675o*n} zEH1|T8rx4ZxNucox!iT?q`&fJD)B{KhlSL^3XkEklnkpIvRD825j9z}_Yzih*W6v%(eMD=eXwkMtMGl0%Ls zaJ@zs3BeF8>c?@ww-XZVby+?+a9aMDj;8fJ!0j+xR5_4C_rh47{=j&ZN_-3fi=0uL zg5oe;E|2{B)cBc5!uPl6Z(VD$1rPS*$tFTdJHH)i;1imDwrroYWEP5r77XKP3yI%Pf?4ZIS(H?2e8=rxLHtOxGX-2Z) zDCg{kV-iWI`%I*4wmsRP(8sp^ogf?uO_-k-zWsV`k+RIPlO!$RxR)${ksM!`v(fYN zWv2E7@cLeWH?ZjV=GbM7StOP(Rovs;13_u}Cc{VnJ}VPLtFxvDt|sB8AjGj2yRvQR zq%NHC521I@L`rE|t?Fbx>&;U&=s*x6G8Dbi5wJLliRwQfr{(r}^$LXa-{Io6f(z?b zy33U!#x)TVBGdS8>sPyq1wv_{`U~jeGZk8(%qljTr~HL6p-AT^vcm0G)yPNlLn%ze z8|(UWDUE1Dm})Ukep^jjAkZEf%6 zbsOWM-3{5k`kr3dgQ&KiDlwvafiCpYPkzwS{r>}xdbdjW?eFZ5_G%Bft#*FA$WWr_ z$mPHLvO~M)%%^}5Fr{HUeIiI|iDWO;25VLEih{u?5>aK&RwD2Tw zII5$~Ue+-@q2mTBB2I@SNR$@?Va?7~(_FSb*L{IR``yajb`f0k>y_*+s0Y?_%z#0t zF0{oM-8*XhC~$iR%thPD9q{HYAq8%A%iaji{GPu^Bwcb6$}aE+F2WwOL>gP|1My!` z2~7)l`5~CB=9;P-fpSa#aR-9ef!(z2%O8j-mH5}Bz%&}ADyM14=Cp7*G%cSgC6b84 zu&G1VM#+oaYMrhAkgv$^<*tzIJ$%PbVZ7a?#{ZeQ>!FqvD=8#J8>G+rdN#r0!C9Np z%&%V?n>d2WLLz!&02!j_MWHrKHL5kR`B^_THt1Xnj4|!tWJS_O?>Hj_^FcX%&k+-N z|I7B5pdSW#gD!i-x)6oBX|!PQwDo*8=}f^_5(A5qhwiaKb!4C~R#epjkS9<>H(vA% zKIrj{!>*dZ{M`?r5c%)S1xM`Pc}eWrSVAZn-2@`6&`aC>8idp?SETq2lKFq9Ox#WO zc>S!Vp91!k^w`o6xG>DzyS}MDCoS)%Ed(lO6v0X0G>%vC>>=i=Uze=Z!NXA{osrnY z?#J$%TljyU7y3~Q+{iG}1@Hy8^lobC(+IiL$#Oh@Wm!&iMTuc-ta421By5A0ODyVn zo!B-8&x(l!i$tnfGfjlTGO0*l8hPuz9$QYU`(Hgk+0^vX$s5tUQ<=1!+HqtNb8*Vh zSN@z{Y_;;AoS3wUWs8^7IenOYE)s6FR>kk} zuQT#J?}ahYKcyy7{`^^Yk}FTSQ$<=cfOz^B$<4%!n3Lc5p5DvUDhutAWN<6tY}zg^ zm6R*^g^%J}XCU+=RaJ%4r5V{zJ@U^Gq-JXGm>i z(C>nlLgU`NUWSiybf@Cksqf8e_h|C|(ZFI>i`XZ(DCi4%WR192Imt1X*2g#l$?>Sh zt2jUYE*1zo8BgN~`g_L*Mvr}%v!9jUqN4P9H=TB%!DRZrz%xz7W-ZIjb|bi{075DI1)Y}tyP{s zUN=d>CDPvOB0k9%yb`?~?1EeeO~-%RT^n+a-zBMf)7#Lk_DqZs(XQ2mU9+Ne^6%{& zjqh2R{efqBzDZr#p0~DV{~o|%?KPK#W{bYIKJgfEA!V^)ZRS#aCKGt+$kPe=Ne|jj zQ*t5qE4%LXe=Lf(A4o5xBu;a z^^U7}Dtq=?{I2dpoD^%L9KsU_+@PG8Czz=_Wn6H9aT72^360rXKR zfM-P4SeJ>a%nZLA{6SXSYzf$AlO1 zbjs)D(}WmfbZ#I^H2%po`t(KbS2J;5d21vr3>`?-akE!kq89`I=s^=~xb?MP*)4XW zQ+=D!oK|8g_LyWYkFBimNw7=i=MvBl{c+KVLstk~?4zpN`mmDfQ&E>hi>As^f4nh& z93){nXtXT{Qu_qh%Bf>&nXJ6$QQ`M@T6Q?k<6d*`p+2H7mK4ZF3IK|pDL#WGD3amg zG44VAGAAL`i*>pD(H8s-)isO=M^V(%TfnBIZ0K~oey1L3Ffx^hB?F#3RIX*kUkM@u zcjWgNIR1IG!U!(1uCqt7teWyLjyzU{7r7M*_a_}rF&jm1t(a=%XK)gcd3St-dWYoq z!kB*hYBHbhFK#%y)WEWYaF>>c*X_XonGXt(y0I@3G;XJh($ycP4R767Sr3NPyb|(n zXO0w$qz0*`14fkGF$&P=&bo1F@aNBgC^&X9#rj+G5EW_Ox`Zq(N>ebbL2&TgB)1AJJa{Sntnz}*Q2Nl zL1|CPs=XuOJJoq6@AZH5;Jn;m>Ew%PGSYQnOL^|=Zi9g18b-Am+PW$AOX!jdHqa0K z(F3<%F#?ps9pCPx%|*xr&3sP{ zlPkXQseR6dJjXZO!*yyvRempl6^O@=$ROYNB9a_7GyN|TmTe7`1^#kcOlyX5_)?*z zKB^BA-}}5$XP882es^7y}e+hp393bbR z?m!GCO!>>JVV*aezPbM-SMydxLy;zM2_J+cuTi7@~s3ZSDA<{FhE(-hcp-( zXD#lygpl8c)I~uGYxO^9&rCG3bp4v{4_|?VE-}fCDQni@a@vx4C*`DoS9fug!CeYl zARmvg4>xjb2@fFsXN2Qm&+nkRYT;n*<2Q>9ilT&Sn^+t?tQ4i4_@}%kdS3$7_@%v+ zqplj|!!(kWb%92fWKhBp)AUf<+f|txtJ-~K08?bBFs${weut5$wy3yA_m~k3)P%vl z95rg7kKI8B=Tm`9#M{bX>mrj4vU)IwO+GFHt1O8h-(VN<6c2qfu7xB1NfFZF*;Qsy z)?qh_7bm%jz&=uYqhF+kqZ6M1zR`uwXzUG6Lb8S!y#Culocnl6xzE+COKZ1xJ^JTt zw9{LV62 z(EkrIW)Rn8rFV;vIrNq&g)Qkn3EX(^nO+%KYY^>Z(;#_v@?(NN{G$h|(mO|K9w~J}Ou$fl}HAC2t=t_Ho@TQe{m{&*BdWZ0= zs(MfgsFMP(p$$n~_}zEKao0STWKSNFr`xI>AUmGzua)P$FECF48X)GZDV$;xcD&@H z6YckQW#emRqcfOoPa}5@nBt!Es}m5?e|m7<h$j9TW-2APyht6LOJKrj=z7A zpRdnXU?|dqkz2V3fz~t#7?aKW^UkLtRl<&8^PnXX>VsfW|aNBrEU;870RZov?jOVlyiE^t1^`jRHT-)~YSOMUw(SvzUt)I_v zOf=Y8rVg*U+-&Llad; z_{8o3mR#XU56m^6`rALP_FMi6+R^Vc30=CZ)lB{m??+KnQ-SlryIPR2p1$aUv^#z= z3a8OHNKf*}6|g_@AWZ#_9_++hy{xq&{qWl)?IiiFBF@iY$5I=~AVnca7ZF4w*Cf!N z`A_IGiq6L0Z;Uuc`&g?j*seZeK9bMB2J1-2m+RXo;cEd-=a{thnN$ul2xJ(I7uol! z6H0~?H?*C8l?(M1%nwM7pw>wF?vHWWC52UbmFme~-eppT)TRG?f1vdUDcdy%sTq+6 z8X;L1ZDihLY2j-WZVXp}y$wzQ3Nsjqux%R2$|fyH!vO`r0#$VOz6@gVfd8uyq$4~ zH447@ox67$uPC;%z=`S_|G8aTQZ7U_qIjaef-{a)uB(MiEdD0d7sh(2v}D+D`Cvx{ zw8C)MaO%Vv78D5klv7)gu&schyy3nn;&5ozpp^6AW&x;X*b6WZ8B2r7gH|iMfT1{A zg*mP(CX*EbLS+74uy=3BK}g_wULZmIndR9GmvS$6#=gc8-o7IP@ zwwNTAie*_}LGHyhN<5_88|SjmmO&ywRT;$4UQ?Q};MoWUFYB853|bLV0`Z!8O4PU4 z>v?oI(pYOx1I(KRnf^Zm#PwoE3ve5yoIfW{l@)LhKy!X93@rZ2@RZ8-G(hI%*gNh9 z2ffHj4p)H{hWJ8NxPZR{goy9%_e@v_V!r3ACmuR++EFql9ATG}{Ux}gaotlE_Y^+; zPtm>XNY1n5$jz|sezAX|vn`X+Xe`Bm9YA?AqdixUp9LK8Y+-oq*@;J6URrxZ`YTgc zA@0xpCStb8PZ*Q(W;oIG1+*&0;tPy)<|(AU&6W*g!Z0FIo1B-Qha*it>jZP0v1l)7 zgq=kJjh5+cJH2EYfkC$?aM}9`XkA0EDf5zfaKzN1|G61M&fbW~!|`3-mLW;37aDGc zCPkCYR+$rDf073y%y0Szq+Yu33uZ57Kyc-Jy(qz*+t$oKlo;MpN%gBlD!*FS_zVbr zQ&z7&U-Gefn~msR)dQC(``7zBziIk5QVjcX7qnVO5RwT`T)Bu$>ER3-NsU-FT3f46 zhakhx>a-g2H0iG>zsc>C8al2Epv_#4iXXQ2$ z`JDJ87@yJ1J>`!S_V05QM@2J0w z7L;^n=@xtnE(w^82YZxWJnlduL8I8N1fIuMZC+L~hdMtrEc!q+x`5W+f2!GaL4>4& zhZ4lkir}*DUm5saVs0-M$3eZEU6vfKz%>JKl)>@gV}`$3KbTa_SmYu}7ebDhq()%d z;L+s{ZQi{DGQA)sslJgJCi!2&w?H@Wl24Rzv9}9kd(t)D`}eKR&Q{lgg#M8G6#F-W1B3dZw=IRBP&k{{n zq8IF>KyHbMulGCPvhL4Hx66o~=4Xx`iZ>=j^6tlrtkO8d(PpOzIxItGkcaa*VglF4 zf4c2F&9dQ-%9wpbmVN;@m7*>QZ8c7}WMbd|RKfOS6G4u$@43jxfx}Z_hDs%^6s{g? z7Gcw%CuV?upidwMFamgR+X|XnNj^~3G9-|82X;_s%#WW z1dWSW6{0cl$%h4XX;gT*PVZs-=%5o7xMsdU0KsO-i%{|axiqMCN7@L@YxQ`pJ{QF> z6Y@(Xe0)ACOmRTbtz)I$jHUWp?DtPqKoF@vmdI}}`e+*~qT|%Tzu4I<0?GU?Uf#0x z-ME-pJ%K=wEQFXxRDLLe$(3vdt+4Uwme+a`j zZ>rN=5Ll2n36}s~L8jO#lO-sN@Jg)K<7g`+J?t$rXBl=Uf7$g7fjf(vqZttTFA+tK zxn@W_BJSmS@^l!wO+)P3 z_HgvDG0uEOJ+-IBJk;6uX`K)RLjv)`Fo0F@zyoHixA!3h!Y5rPRUjuRgx*`F&6EEP zkYo};Vv{d)#nbH3z5S}8(T!`$K>COW0+sScF4Sv~B)o~!3HPt&45Tz*MhE$9snuL4 zA@cFSt;>IS;Gh(tNs&LvTqHI=@BL^F z)F}-|^1%W!TjTVRL2KF=yUi`@Lx=AY;7NIBEQ=S%Rp!>9c%A6}g`~Pf%{-pDF;56Y z)fBTnc{+<}L$y1O)B1*fvXAz=Q~(~sM%h%M2_w!5YtuY*GKqNel180-AIAl6o5UC^ z>?&*(fTS3@b?ZukXk`M;S98m4R};qT66E(K`p{i53md-!G6W21n8H7+J1e?Q5&lfw zx=yO;JeY@sBB}gK*w+o}XVvIGz>xk>bJeZhC?Kpu--+<}qy8JIwxb_@6!aVQ*|1AI zNifb7ogiXDG;IC5!PsiOap=IhddiNC{a~yejmjZc(T>raee&-wTON1MNg0FbHZIxG~yeCx8x~vPBJ(=NE+!@=^yN3 zTd)m_r%IL#7Lc24r~N8jM?=q>r*_C}y6uHaSh}GYZW7mB;h{fHoX;brmpOlDoX7Fq z6%(htIgCrb5kIxNig&&Lh^A}68Dl2C4h*xEAs7>sdca0Gm%gdJ z@5w4&4Wp-vJ90!G_jJtC4+RT27WYRbUwW>@$?g4|MSA8bL@NmD>n%YPNqtzK1fDYK z2nE;d18FAxlZ0Gyv|)aKjhj{ku29#{*xWn(8KuUd$vW*~Xc~a~1SvNS*qfo8o*P(y zprhSwIv-KI4U&_MHnI9fLsrFJp$>)wg85B@2Yb*&+WF;ubCXXj zC4yFh%x)agf`#VjU;zn~)urYfI$Hf{oWq_zg*O z2$P!rWIsI5c(_CZacdmHNBba9(Njtu!IqGCj|>IKHX@RZ@m{vfJ?&Zj4o5lcUK1C| zFd1XH_yoa6JfU9)xEF)&BuWH_q&jG*wT#8F`XKF7;q2Y;bylS^u+^+CGO`{5rk8%O zUCYo~>QVl0TX;MFH73_ccl20T=sd^A0g|G+bST(&JLm)bQt1O3=V$H6 zn_c! z8xbNZR=17l6omUEs9NrTRQ=RREala_+^PO?{*C-|AlDB9Nk7XP$WB_W8mMXJ<8s?* zYvMu2DbcT*jus+MGo2y=OQ=XT5@z3Z_bp3w{*+)vFD33gOq`n0mDz2}EwHxm(i)8E zHxcZo{5h5QAg$qk8TtGmpu#SmMNbuFD_Z}>tW4Kef>%P&m8Y>)%*v{2iBkbWCWyagWa zIdDqmZ%AA``sCd+W|E{b3)Q|d3`$3zd?oM6<9;|KZ!jd#DEnU?M2|BUGk%m6mO#r5 zY%?;+|CsOcE4W1WSWxcyIkC_mm|j-AYEl-3V4FqA1$q1WMeja=lgx%2_=1 zTrh0_sD04M)p!Pjn|E~tf-Jq^33zhaADv@fDryXT+uR(X$xV;T^2^m5#4W#?3?91+ zM7)04uY(U{h?tHfrK8o1p7MF$iIP(Y>Lj=sZrXsxEcA8)sd?qqGzU|o)n^BC&{q{5MU05OL{?$9sQ%mi; zvKPabV4c&U@K=ELEmU-@YCDz%BmdDfAuhJ<C3dVA_x<%m@ccX|uj3uOT`{yK zYHffBE!ZBY7*qMQ!PwU^V#alvt{?xJYRbBQ>YTwarw+px6r1``(*#O1vVX{^f>)XI zFc~k5l+}yAeDI^`v$E&Pv)_ThIfQ5j+5hoCD;cY=(!w%t@&oiY?~oko#$VemfBnLf z#c!`?(xt2K_}8j*2T+`hzm}p(u8hvI7QDSH4+wY30DsNzy{FpU>rEbLy4Bh*I3wNM zKbs{p5SUyVuxCDAx@!Bz6=3>2~Hoa-u&!^W%>@2BkYqnUV^Kd=@_40-P7w@G;7j0ExeqS1X zz<#@;SYUMSlr$DxB5=0;AJOZ5q4M5SH@Y<@nl;p%W3Z`VD?9xa5b|=yG~qr~6{05B z*Po00&CmB}iNR00))62iF@*Sv#tgx~^jkp7Zu5&U*Y9>!$W9?}-`mOz8Tyb`gw`of zpVh6!KCe6jp1B|vP)NG@GEwpKhk)P-(q)a41SVeGk*Dwx6zz>=A3U&v`TLd#qa~SH z7EgKVtyH{1oJljfo?_%Izm_}TPePd*2eS&_sY&#((c?+>whRU3Lgw0aD18+4G`VVJ z-@J>llBeBYqSGedmh(zK@lC3rB8lkljtOLL=}K0Vtcl<&BKK`!rw2-#pd?k{6^#QX%KQ_gk#j^+?Y?mG^3=2*OdkcIA-C3+q=ssy zsfuln1-ts@SDgb4YtbNW58ZRa0>dI6kXYIb@Br z)2+NEEM4t0Bys86qTrzI9`VY#%=wIz%c$vH#(v;fZ}7QX!r`RGOk~%0_!3p_&{v3)<{NRIVUWs-7luMHQK-Pj`7r6g0H*CIU=N?;RWjFpM~x^EcR!3y`c2PNvOiHx-vKO7*HBVPpAI`S&ulGooR*7virI6~=GoDjdMF z|55s~+pc)uJ26#=P{9!6!EzggkQW|=AH=r9J(8hUb+JDJQK{PQLa{nsE&7C6rf!bT zETNm5#I1e8Foe< z_$VUGdXcSXC_1J`{E05~&P%B0>J@ZU^d_6xdx)s~IOQ%NU`k&Q*D&H5IV`oUt~{UU z&&m`N2d@VQzt~QLd|0Txi3UJ2VISR{?T%O9+uWr<>ar%qGSAL^br*nZ--P4P6C<1m z-Y%dRHA1i|9(Zt|Ao(027-mZVo?OSna#qbx&g2HT5JAs^H_gK*G;c*hgIq28+b>y* zwKwe+XdSv2AMrpj9#!cFHeqL@1Zt+<#22F_qZAioxrM~|j02Y-xRtWMn~!;ye7~@` zx(!9n&#on}lS=soEUvMyFcd3>1(q$Y53u=Y(i-Ck>pvDK;?rv55=`8wDHhpa+QJ*Z z7Ew}SJ!Swp(jo0bJg!eXt>({gQ(R|$+?eEp%J{Rece|!XI(3QmaRR^s_WX=qb-24F zQ>ZXeBGt;=5a!iq!2SfTlFLP{Han4+hF-_Vzd{D4!7K%oHw#^%F zM(p;COvoHQ1{hoCv|AL9ikdG>dDjRlo}hM6=2P~K)9qfZ-6G!L20h>(F)cDsKb7ku z#v0Z2-gnzulGT2{K#wPK*6H*!y1yBx?c%SRU|+4hS$M6w?45YoNJncyK&BE2@dOsy zb=g!24Pm1*8AObrwIwB#Z7`{2VYTZPG()R;R}u@{)?JGF^PuY zMSfUiYgx*m`TV_Y5zV+tLW{A_`gY&m0jsR8|DWeb8Ogmt7nn{fPA(XW{fr`9&oUwM zhzH!Hxo>$EpVVWLeU+m>gPAvhRVoW_DAgEQNuWqjddmuq$^G^VxTfBtnMC$OcUSGb zqtcC&ZZmMHhvNX*Ue2Qy=_=)@dUP?0id4TI1LETp=bWUwF>H4~y55 z#ytXv?&!((++#2w$;@Ul6Zex?t&7{q>!fA8@NRBD7dMf!Y%mXWw78z(5^2e`soA8q zullhRq9+@fah$4U-&4cFm<>GcW&C6|IJcu5X19ix+|1ReD_+>2QPR-A@KNkOdr;pJ z6KKnL8OJ%!y_IcSIyC-fvB=x@*LsL2uKO&UD~xmMi?^A;>{yT*>6I!))5w@LnxE$a zAF060wx?7MMiOL!z5K^@A9}ukF(ul{Ta+&lPgz=k=F|=km0?p#e zJbNSTDuKGg-`eNMbfXteF#bSLpPFK6AKyD|{;nNS_lO5Zi>arCZDW&(_jj3xHsO6m6{7j$zMLd! zYur~3B18|pet$M!117DmlCRdMVq9k1d7>9|vgmT|BKt5FPnfKJK&!n?0J0S{*Fm(e zzu{&T*}CGs*#uSQILW-YJu77NbKP-nDjWnPe2LQM<=iq8-VZU>d8IGv+nK?O7W4`v zg(>B`+$ZAyFz-az6We#(HBo?$80(-C;`*+eD_3Dun)hn_Gnqgw0emS~`rq6{{i#d$ zJyd!;9xVW+F6J#-K8>c}!s-5EvQEcY;@~+jQvkKuNi_7D4raNcWZO?wr0^uGXEDT5 zSA@e%>_zS{%i=#mTCcgm*~j|auKpwyX_I<`G5#&QJS}dZg~SL?hU{n3j=zvZIjZuT zu4^<(Z@SDV<2WVW5W7wwCqmdFlkRaWT^CgWFOy4^?Y_OCRcpOZ>Bv~i=oL_hwt5A7 ze=wu375KtPH1QuHfmjMd|Kov;k|HWpy1hmrN9}Oej_J7%@k^E95--e_|9HT>L=B+< zNuauU<-MxW(!F&7fq4Ch2l`I>I|8NLZPeTSuXQh5~&SpD7ep(-@Xn^G&ZC0`XrOhn5iB&j*K26{bK|~EMKoJQO%UC1TUf_r$?xu zAJ%e<^ChQ0B^W&qrh&wr6Ob_T-9X^Gc0MyFh&rWdB-`pQR5VV|oV)(lv+}a9IKKFU zH{WRE_t79j%}!7LCoRoE_9A3JuA^~mHT}!ZIrgEK{zK&aHxE#Lno#)uGPJDfzmtNu zBf5yjkml++Ki8=J$`<L-z-4&1AWA5;xyyzh`uU_n5bSb(rma z;F7X`fs{QvhVzr=S)mhWcr>HGF!h)>!pi|Pvbsqy4 z*BlCn3CvR2kweu-Jh+Txjoxc@#I10CCMRf9yZw%i>bmj*cmOc_NAn9Re96BC&yt$< z@}0ksp-Or_iUL90bxCcDl3>K(t21n9by1B}V6YE$+)PxTekX3!I1t?^K%C>UhXdl_X_fR zw<4$bc=n`qG3Nyv2Ee#`Wyg!tAw?>IRHx0!jXJNZjE`*3q6B+JmxB*8GHWv!i=P-Dbq=Kd&@jzxzBtqmR4LmX~0q-MwFjdA+1^Y=Z++<0S?w~w@G@E;C z#zr&|Lh%KPNlEb?F3>vkhGn!C5|ki-1UvucS#w^5`6(mT(;{@FK3o@+o2ysA3FtLO zMOqRrb6st0Rx4zZ~z#2HY1E*PdNSL@GX;B}o-8q$Z!clo3BO zcK*h6IZ}pGI|g`9NOZP)7r$J3gwRW>;!$$#XYY$Cc-d60Gy^CGt?5Juo57Gk4P+0& zfd_k#tG6j*hjGk_6hPjl?x^0c?#Z+Stn#C=E~QZjhHe_`=2p27-LMHPKi3bt!OyJ# zf8c!uSwQU&Be5h79aHxyJYrA~=hbx|kJOS-h<(P|hhmu4i5I8e`NE)`SdIa!x?s3lj3JMlVKq;Xr2W zmIXj`kK67!19!H2kIwPa=hS?nYN)1bBs1hY6-7E*_JaHJU;$6v?c7Y-9C9=?H{`kw zKXZ(I84ij!Y{4v%3F}fC`Q!)2{>L8Fz=rfRbG9fK7Ws~f_M0Y(ohWo1a+iIhDo&l- zVpAXknqrtc$4q|~!jUUBm^yW1&D{--Eej0k?~N9~=`FGj1^y$ZzN8oTd}gzUui9#F zSd^W)gdl8m&4M^?pj7VXzBUClgE9TK2MVT{(XL5)r>YSOVhVQ$)AlW#I>$~wdVhW4 zU%X$Q0}AsogxIk*CO@cry?3LBLZf8L>u6gvR~ak0yRE<=*PR0gO#ekqAX#GYA^!J$ zA?|k8&uo9!H#YdvYj6qiiS$a+_WuUXr^kQR$i*}5uOmp1QWn;K^4V_MdBg)e6PC(E z2m=)3)A8Kh$#UnPbS?(%_D-1fgJb0JP>Bg*z(^{e$tL7VZ%A9t9-bB!>%6` z%|lgxW4A3h0O(xN3wVxbaSN8jd3QsmUW>7Fhm;g;Z}y3$zd4j7Yg{oPV4@Hkr?GY5 zt%<3{ysS&~-JQ;xDuI}xkKO0rDmVB!+rd0ov8!oCLuayjPs;r~EjC%RznoEYuSQe@ zZ#0Y|kUcy0kze?#jAl9aXm)zby&3* zyB>)GO9tt8VGwlr(@ZpsPMVjo&y&^A=l&7Xq>XXHbC+qg32av_pt)kyg=aGbwoZf z7V&7?znr)_>X_%8X7g)^<*Vd!&30pxJb?{xVtrNaoUQ+S_5<@em;Z6=j&#vB{h4df zmV&+!h65?KMGlw;ZHaHg#1eUbYC?z(#W~lU5iA>3;!SI^@!4^^;CDBRfFb>{2Lt;} zv&aiVkv7BvadjBwk`3nAxQu#ljF|O0y(aH527wCG&4v&AJP86&n7HN|J^ROQ-W0ty~%QWC$<*$f=KN(pp6d7 z)TE^^L$X90X~8PMeW7IhgrcbGCc#P_Dl_g^U#3ERquRVK4nbyIp(#w z$ihG3L3LXqmgMo=Ot&%Y3{n8%Oko~Ra7L&>3mrI%gq`SARkQT8EF^+g9oz)!uK z6sC-4jz`LpftATRR3H3413%{-Sh7Dn*!#Q1n$wCYLOJC7z9Q71(D;cuVu_Y}rSS)Q#uukqHTCd>jbRk*$Lwr8zwc~#AB zbyS_u`L0O*B`THPwWH)1V0 zvQP}bn0~8C1H}(1I#kiJI-`=yb*rjn#Q82)YMNW0hLhzb$;MNlJBoQQb*-etmjaC! zviWm>5jXl>crI!||3Xu0C=mk1o;tt-AzQkI{l3>~66tVa-@j$N3hANO?zMJTLZdIR zN$nM#0aFuHviA4B5cKPwo|cVrbtrevIG_JG`5XypKDdNm@S!2?fd{Ea?_}wPf=uD? z#_&<~Uf!bQTRZ;c!IKL5A%UKz@6y%bNkcuPmD_4Ha=~GiXsZ>^l<08|}-8pp6B|WmHrnSrD=H%YBIO>K_w3^C8JfS-~Vt_}|E^ zP?Db5bm~h@&^ZEC{*a8$vfOxT2M~;gBV0-@Aqt+`Vir|%+zb$EBqOAWDG#&uK%NYL zi5})&LK7YYmD6B{MTV-i^Xw&*Vk%Hk_V7O8Z=&0QKoD-$87*|EoxFS0$4%vH?vFFZ zZ66bgn{_y|7!G5&*?pr%Bx56tC zh1cS+J}fhC@52@26ODvONvtoVIjA+4+$yADBuW4#kV+IZezN_?fkH_nZpg+@2%=|l zomjXB&i<%+NIebvU{=W+v`P&^q_bi4sPm6>kgM9u8w(Va3rErexgO}!@0ouM9(lxm zZc6}kMspBvnm3WsS3fB0%^Z!I66v#oEd4&1OAs-{wg1yv6(H6>4Dre#NjKPO1a10$ zP(ip>c3tAe2XpHmA%WU_9y)LzXq-7m+MG(PIREPF{ZD+h3wX8v1J*llm-jWx^ZCZek(5)XaLToVFOr8ls`>Q7}+n`d2_< zhF=^Pa_g2$g~oXrN81>&^tydFe4@MSQm$a@A zImva7Tsh{;qf`wAa(i^`WG_hi5kbx`NL4=`uYFi@XttEBC z&}>XLD%-`lgNm$}>UbD2cDiImI%3bkGBzv%romLy5B+Bos?_hCnJtptLv#6bu(Px0 zep=~G8#&lpX+CbfygXASjtrrcFU>=vX&BgWwki4K2c33UGq43;o%E9Z6hv$g|4qh9 zMN}R4igK_z8TG>VXpSwsIE3R0(O8TC11RPsfn3HBL@cewrQQ)Qy=i1xEUQFDwC#bB zx5%EK!dC1E4hCZaZN#sFRq?=s^(%Iz3mox%br} zHb0*D)H*qPjO=p3sSj-(AYhyDcPgahJuC^DR-?nxbotbqFg+OAV3P6geMwVdL=SCE zwj1bfF0W2lco*uSgf^(f)ZmL3(H+a|(6P(XHvCf(|3Y%s6>?W~>x4K@zQ}4=D@1$! z3tIpE-JK%AlOomxDzk6EBf3P10zZ0ReYxmTFKek9w$+G%&iLsi$y5fn=U7Ls6#+mR z9ANm>v;+MtYF6{tBvC+rT2b)itzky*5iSqJoI4FMSm%=ZxS_Q%hjQA1uAD(!AFkko z4SdZ6RyEK^E+mi}mG_$uJ zL~M~JTmY*0a%%Mcg6(=e8nsaZhVYsB-S!LOFTzXCLp6Zo^SXoCN%Q1*Yu0C@t#}&W zUH<(?x)Z=QXW}C&5~yy%2OgXh|Lr`ep!m8_UI{;-JH=J^EJxp*oFWsbBfhbX1}p1< zDV=IM_#Vkn@)Ae(e^ChZeWD#yU{XC%cV&@K`wwL8O)6XZkDrDt;&Os_IuD%+`YoUpj z;B+j70!!#*Ki8O40ji;?{UYwiS*Z5& z;;!eWV<<@-JE{(`)Jn)~=|q7YHtZl`$l-$_+U^mNWNwl(fq-Y@ZN2Nqy>X0%_9Ina z)h%R!Lm8+A!9(to_)Z%ywn20EXC!W%W%?IUWYrTD$kCOyzRt&BNbpccFF4Jt1Cq5> z4n<49%JFh}K`p%bB!MP$fzBXXaRXM`l<%v;5FP7DroWFcwgXzKXxoT6CEiGJ z?CzKG8>K4$;s5Rxj?d4hQCL4S5OYYQz>q+1!XE|%J*@H-CEl`WD_*{nK15%B8k^1f znl8A3=F!_^B0%fVo&oxah7sg8qIV*W|7nQgBfmh@?(rqOH9G&-*n$NMZGD=O1-8Y< z5@g$qx$O2+inRyE{o%nZ1Jt;c^rw@CSff>=?BY{6vJOAF>+ego;uBT;D?Y%JCc;9F zmx#Ax?)6-`YLnIF;Yq(_RNhS-g>%kTJdY$<#0MmF4;{P{^F^4V*S`@V36(*0unx#0 z$g<2KuW9Dq`9@9&mhjBs$YRt=ioZ;%xRGAJE*@%pdQ%`fpw zH=8QAgh_z1(q`jB3ZbAkseB%i5QG5{X{?$+(T9MnPc^MVO}W|}eAQ57NxPC@ug%9x zRZz1=Q!-pqdcr?K(tAk}DAs_LsG@~ZMo$i*h}9ya=^V8A9B7!Mz%^R`@fQ+85B=(g zW{uJFv`^Q>5N{~0g@r3WRm*I2ekuyYSzH2gIv7dUzS~oNcC&|so?bW8B1841zL*Xv zp}pA!oZ>;59RCOjpgf0d~e?=S{;@zaQxc$sDGi#c>Ysp`f)B`TY4wR0e{nt_IKIu)x-#tNS zw`pkuewCjGRuuO?Ni!&L0eg?*mOw_P=1$LP=uJSwKs%YRYPt*uv)gFVPUo`xb;3hc zuU=J1H{bFYwyef?Djnn8FbeKkS9%?a0y(EFih^_F6fmSe)Lds0Qf{}twPoasjPWlt z(UW29tBWzgn-6(v>G(n&0v0ImR>%y5%!#dMdz$_Fr00U|#<4wRMxLZpBaHa~^%vU9 zf5c>F)f;yHa!j87MFWk5u!gspYxLk;K*wH6mh-{feDD5W9=suejp%zR3(E~Ug4C|4 z4B0|NsgenC`icrUje+JQ83jCyp(vo1go|`+ud(Pej?y-hlTDkstGPV6`SCiu4*6yc zfJ8;{2KS(=IF-3**zAB>%ZM=!g}zP3^JSJ;hwz6<&_f*xXqg85tG}(j9}2^{jy@Nt z)T!R-EB2PauMSe+-5TAWrhh--lRNEne@tR9n^rAE38g~^JU?_zRd zIVisKhDJ9S9msHY8r7uiJVAK>J3s&1;NfeV4=Ay&zw;h_nI7$xZyNMWRhJJjc4fmz zG(|hR2pIJJc3++)<^#!M8RKZr`se|hkVqPm1WsbO-GoqZiB3W{AmHR3t1MkphN}~2 z+A5XiS;0*xCH;p!F*$gq9}A6a<`11nk}M(6nICw5#ZMP$uxFzmu2lO!B~OLAd(s!? zDCw^FC^(5?mWYvMcdHA%BHI%cF8@9E8I?Rj+}_=Q5=ehGAry$=zC3p-$1W9K9>8c$ zcMZ6nxkXbz-^gme{PLZLu7pbj8rXY++Ex*EZrw~tpZ9n(O-h&PArS8^oyefMx)V>H z~@zCbS|#BliCirI?hQlh^*=|Xwqrw z>W@gh{!vY!Z{NNXsEVslP4I&-aGGA0zMz-^7v3&j*T;d<}H<32f8mIUy3{5UH zE;cCYTVAUje4h9t9&FgbDou6tQjxPjZtOT*>xK1xhC$1ORWRymPW`s%yE%V9UdtC>)rw_+2iE8> zKH-04CuRL0sLQ*xnZv2KH1bZE3yyX6U`u5#!X6BX`Oq_Yt}8S7J$!x*t&s~IC*Oqp z&E;G}q7A2zRPEN`BNLSzj`9N3YJfVroU|jwF;QIp`HLPt$zt*OvWnl2x+6{yvAgC- z-;%32R$=MQ`FI}#v+f)|ZXKFQf}B>a$_-sF4xlDs&$VVhyE26=ed{yR4~62yUK^+^ zHd@4p64%JJ=&~?iOrYEM4`mMzJV4JJ6?aY!m6qLkQzw8eQg#bUI}0w!wfWn5AZ(qN zXi0|LBd23oLTST#bWbOK^oR#ogeFh;xyCztMmAf9Y*oURK>a&2?8O%6Qp z={FC|rBYprNSn`Q#`#0oLX&*=e?aFAov@!!h9k>0(6j-q6>9Xu$tcd3iuxR&TAwHf zhO4h(ftyzvW2&)JK<}QkBB1mD3+xja-^9ra|A{-Cp3$4jFomqwo3?!xszY^$cwZyI z0un+^K*n^krqrdDwOWU)3j_uC8j4Y`@lyDCH<+y%#DJy$4N0Q+OI_2nQ4qOjB^0W5 zMt0kltHv)zdZZpyTzcB_XTJbf)hT2Y5+8`bM!p-IW;4qBO~v6uux!E+)6b`E{%S|y zdaHkdgpXoJ^fCA-^HX%!5M=`Hy&Ty8S%1^oP@ut3t`~ zdh2tP8_9r%!i~t7qD99{cn2>QU^Ii1xcE~7w*$8`GO|Q7Iy(PY78%@vBuciC`DFYJ zUWR|f1X5sqh=xA!AQSJ}c57A<*r0Us6rrvN(Ro8mKw_Pg6ufC3KB4M9SC3R1y<541 zz{MKl#c+NRNdJfj5Xn|_X;i|wPzMr_`BPV1fg{~zJcc5|l`yj%uC_Waz+dyn;$2Pj zR^B2sDQ1L4GT?J4^BG|Lc=7aCT1C+M1q~J~{XWnRddciZ3)v|8>04xzSFj6um)0VN zHI7fki<@=pO{k?lKtkRU=JCCKkJK{j8qUttmp4qEP05hX(}K;H&KholDGzfH$SU3BA@IjQoc9$|{7MiuWDnjb8?4isFBQtvt|AJBmo9y=_hg9h4L&!zh$n*^tjx#hizYV6|klm*NE!-aRggO}6l}K+iX# zLrCXuwZ2>d0R+{~^O!RHWv09Cb=u%``9hEMw53u>n7y~|V`Li5K&dh0EyK5(f$@8< z_suVU){=^|rF8sqa)E{trblJjLiNuz2g|1)K+yhf>co#{9@(!do$2T+R#jzb2UZts zFXp8YU3tl)-4|D?>m6Q)5@0A+$?60T>AI(u?P)61fyrfpHeDW|fC!{3uJ4;$y2Q8e zi$AO#v_t9k*v^g*`5%Mo4}Wlbt#a&$d8jM9nci@1LY%^g$5?&9*<$ zry%mH$2K2f8ZwkmvlyOku0MM|pRqCB>b2X;O)LJ^?5r%rsvBo_2k2Es9?91*4pVfb z;efLtj{20U({#%2DVd$47B;oSoMiZqm_Tl1;C1}XgJDj8^Uj#cb^7y!zLIn@^D~?k zAU;6lB>eCRfCrF_>^p!vS_%Eo$k=?F{oe~phbnA*p%NKZpSVcoHPW%eyvULGN3TiB)-o@;B;2^c%<^O zj78gI?nES#_AUYqJB(~)fhDS;w#=%lAqRJaCSu^Ah*F@Y&!hX}YKS77?%H*-L6_;2 zHqeRcroGc8NcbgR1K^B31)}oy&(+z;<2ks$|Ey$5pnnJVX(Up94tK+6`x(arCfp^> zYzB5lY}C9Eih=??9503mboRwYLS*K~>>Cg1hwpnXVeH9JGdFav1A5J2~4XU7AC14?zeg*!b-vSCU3{nWTtF&)b zU$@^kxrFEFaK~lf2g|lhi8mTPj{#DW24hT?Z~#R0Vws^jQJ(0DGe`1TiCe-@bj( zrYj7I%Hu~;v5~|UCaL>rsq{pPA9zgNZ%D52mMjTA=7lYx`ER{Pq}MRaRx}*db0H&` z-a>s|ggpSRjcgroBGZ!W=4Na`;{kmTn_pK!xBRF;XW~CW%89&ZZDV?| z;R5v@>In60^D49trPb?m4~aNy+$mpPWEKGFkJXI3N>P)Lzfi;+gb57dS~9hMVCPz!%yL;qgUO0 z!~Nyk3on1BJ-o zE(Hw~l;y7fbo-SnEuQ3J4Gd1xXP;MN%ct*{6pb1>0Qp@SohmPkd_@(B74|l?tAYbg zWuU74zTLB{bRYKFwwz-fqz^Aj=raAz5S!Sl*w`!`?03LK$R$CTPp3&*FQTGuV)rpy zaTl42ar5tUk}_x{CjDIdaQ6941QU{pZ2hvy@-lQo0u|Ia=;f{#q?ymYY}Lj-({5Yd zU;>_@6?8UH9eAF9v{SeH^rD(059Ez@<0bwiVurHN-$@d2H!(JOa6|LUxeUGJ) zJA(V^2^~+bNP|sY`jM~i!HO^dsUq2LdUH?^JJB`OzpVjDcob>{{a__p6XQ4zD%pKX z9}Ed}^!{%`)vd&5g?AkDSLqGS+;dx)XHu&U-oYg{L3kRztLb?}BVQ#>%4YRYJ`b3hAXty&ib*TR=i_*0zeC z@0v&qR?Ylpj6}>V1;lT6_Y&Wq%B3gG+yZ07$<$>9s9{tU0ui{;2yQ><_e?8g`F#z< ztxj=)G^GgD0tEbONLNYR;+20UWpQ35u?oY0De$cxb^CCeo|MERe|K(#-_LI`p>GrGRFEA$b53gb;C~A?h(@f<^M0kv1=#8&V==*Pf z(z=c>1!6dWG5uB(fb77gXn=~2%8WuYRc{PWppBYi_0 zER!o^wjQ$Ic_yWi=MN7TgI9U{P417@F#7_yo~WV9$)*beUkwakm8t4!zzhX{f?JyB zVygZl{Bh5zW^(?Re|>)7(?>i&ANR7m(~c`Vr_?;{Z_#yWT}Ee@X__A3Y>hc)FZn#AX0s1m7o>?WW+H<$kq+8RrDRUn zj!U)L*uR;4)o98YR@{Hx{v6P`FpOKiJyFEyifX)w7R05b<}6>C0d+HSH?;A-tDG@4 zSm%y6I9`ZiJy=6wvhg^}M6`!WSV#_I*Bm!(M&ou`sKH>#{utqyXoEMVQ`L;K*go)F zRRkTc=DS!DZecU_9TPWT-w5IY-I5C4=xb=@O0qe{jmFv`w@J_g?mKw2h+@(~Z@r4- z!vUp3>tKCd`;dxA3ul)~$vif%uxe8CwF&(x$POOI`ANb&7!#x8fu{9WWzantr^9ou zl#5AYdWW!YvDp+py^hioh^t^sztv=VrToMuT^}Ru_z>*Nlv%7p$vV*7e_fCV{-Oq}G3t z=aiWe^{HKWGgM(_tuOQ0J)c!A<{t5YBEXPMsaNUEe096*9&*X}51wHI0*iUoLRbHm z4NT>TM@pZoyswuPs+FFA8N&&q%%qa)jkb!73uB7KZF48QO&;u7t4q(z;%{KDA?|_S-j<;j#NzHP0FleKd!uV;!pB<3q z4X*n57fhcw%lYuBOhoiwg*Vx}Aq$1~GoK1e>|j|zp-*gAYI@wO?0uhdO1kjs&~G?u zQK)w3l?hKJiMFcP`^1jd}2*8O%2>g#Vxf2IiCCDOZ|bmMX)O&PIz955%~1|3p87Rk1`dyXV`XCg}%s zUVSxox&P*S1zn_;L&=(f0}Scjd3|CUzc!{fZ=p7a6~-apq;7 zIcA>ecKvI0F^;{d5_$L>^)w>pj4Ce-M9e1l^%^I$Ey0pQ9IapRw&p_n1Y^FR5Gqw# zX$QJz|2v>^^faGz-G;__&&~s~ zg1VV^x^q>(q)4Jpy!J4iaVK6{SAJBF1UFWSjn$o>*<0%$EgX6QJrc|hc4UD+&$1*kC(Isy~ ztlW0%cicz
aN_`m|9DCg7#)C*6>^5UJTf6m<&TxNUaZNb2dWL1sHghlf(#r6+1 z<2fzQy4>z%Y;wAjcw|=DIQY1XYPgMX96T0QkN5k50tAgP!lWMo3L@ENace|mPePsz#cw9*SUrp@T-QYdGAMd(7l&f4#7b1BOc)GA4>I{B_q?X0?oc( z-}H$PE=Xna>=G2#4s}!Y(mV43C!pUvU_dE*hnik9np9As!_t>npF*8M^`lEmto&k> zz=4tgXqtUBrM5$ch;||Yvv3hVez&snDerJjP@$(d!zt2$&R7HxFdFJ$pHIGbfK(wI z7!GRKRjH0qP{5m+bFC2#9Yy5vFu{USw2+0+zWDhF3Z-LAvRGMRY!Dkn7P7o%ORAke zDN7Xg8~&VgB)Q05fkXw!}zh^tBfME?i)hTVP3IFJR@4t z8$H8(ItaTmz`)6e0)%L^KxTD;QQdib?d~Ty+_TvNH)s67_ z{z9^`<#Ai7LXitP7U5-)g-1%p&>6p0wpmus-7a=4`=k%_?fLS;f9yCUg$w zvd~<=AeubDOg&7LGEm9SVN3$9RkAR_wy*QeIaj<#!&L)SRNl0c1COiP!Yw4N6XbR3 z18UlJcqflQn-=D>gOa0ZxI)DgF?z#;)<B{%CPr15*Q(YC|idn0F8s$AE*;|t@T z?;|5WT$s~FlPmvkA?}zORL2|Jw_%u0rwc5*_E zVv~%t?L=hn)KEx_+#TVN^82fFu6H>VHy2-=fwoJ~GIB}7_0M;K5}$P|xm{G}5p$W2 zfpF=SqI_uEMpxGU^n0@|p9C|DxQ_)to{N)n2B#`mV6g&9x0Sg+;@}+aT-FRZfr4B--{&?-}K6(g z;MKX`?Y;HXsz}%urey#sKVOVBjSvg%MdV=|xoOmheqaQD5 z|A{~Fe#;h*8bvwzc0{TerRT>Ug&Z^?wrs#>U)i>s=rlp$0N{fj417@<8@;~yR0BVqU2eMvBu_}pO>Y_5VDAgE_etMRIG9SuOf4ub zYtqT^orEWw4~8gcIx3v^0Jq7CegGi-(?RoQu0vN};LE{dK~?N4LH^_q+=I8*Up~~T zXh&3EPRs#U|F!u4kk7>(U7D)jg)itQ1TJdL~(8k20G2g7OY1&g6MgHmvDq&NY7d;>HKSBZ_w%`9Kyw`yZ z!v!(uvL^Le1f~3h)nQL}C!9CLR|8!v0dDmdF1ZT>aU;GkFP0OH2h!J=X1^zQw z!=SEc_CqmGUc&USFEKt4u_O3A1cwSr?U8EPAGT`x!bQOkKIi~;>ix-#oMgqR4fIiXO)$64 zHPZ_sBdj&M%-Z>`CwfBBym<_{Ac?NB=-w|1&*`J)Ro~)%1aO}1HuI@PIo4*@aZ2^I z`*#w|uyU3eDf+fHWV&#I*0BjNB+$nnU?Lv&-XDll*%ccN*Bm_0q7{zrCOBG5pyq>$ z75RTpe5Re&V_`#Q?_Rs37haXmL4383_Q*LDN8}(cF-o6R3Ct=3zZq(AP@{eum+^R* z&(%^R{qXs{5B?|gT0-5fjTLFR+b?eZUKSG)8(!ovhe?D)20NXE6r=_4ATo{++|}me zE`L{=8hoTzDM{i=)GE9`JLKn3lLIc)oyP#u>!rNkxGjI%nLk1&Js$t_E8Vml`rL=d zUaitwR&jP>1Z-hdZ}A`17)c=nhsGE=Vm3XUQ6^_`B_h#^c@(v(hK>rbZ4ad5T_$Rz zxOnR&Dp%eeVz;*Zwp!3kS5Zo;bh=|+=KTYl3leb4u!BUN<(3QZ^a>$&9M^`Rj(x7d#vJLApi9!eAMq(1_+}== zm#&wbwYrt|^C@2P`@Z80FM6C)s;zWr1E(i7p|5TgY`Dy+9p|4X`uI~YQ&Tl%-;7!P zZF@06TE$nX&JH4jnTp1@-}q1#B6QqqZ+x;RjV1X-r_OP3bgg@D_-w5PfRrb#juVDn zd3nCY%j$zsFSw?UUaihfo|a_)Hd!*Glj&XuP6inNTV--TWaY#4VR#pt?O;9nznmO*K+k3odH^wgL@J0Co;ahz<^ejEdB53vI)GjQ z@ge`bl8F+}Tk$YeO`T!an2x_FYx~!O9+RqA~O~D^kSbS4e3{oJnXV`bs%nf}Pmn_^)FM_={ zHl<|3`?RBfD|NMM7*|Pw1L6OI$(d?xo#L8+i#MwwnWs1MsEvBS@w_UkZX<8}N{~3y z7L4gZn$%FC;)KI+AsSvg&dKb>h(QRcG z()2kb4JgqaZb;NRvAepTO{zZp`9hviI9>vdHll`#IE)omuhCPA(Dq_#mPV4blQ6_> zX*OmH+l^ZUA;Cfjs)j%;y*RUNcgwy*>(N;AZ>)>w2JES193B;~dGDv<-mG|2PWLv%^| z)7N9VCCgKp3fH{Tt8Ehsb5+;Nb4cFSttXfUci`CaNfF=mSeetrVFcRax5DwX8oIxj z#6|Cw647)60>$M&bsV|#;cBpBG(R1KKBgiHOG0Up5q?i?wD+t_1p)-9<@1JgS;fS* z{ZgeP<~rZ>DCCSP1^8AVoFV;7*oEAj(p&7ovM?W(zG`jp9Iw4T~ zEIElx;PiVjhEL|z8+5q}w5x<0&c@#+zd!*>1ukN;V5QgmGUBR2cR-#xVU9>~TUy+e zt!MrA0v#BQwG~2uXY>Ff3>;Ff8@?glrE5|av|w>x5W;I z6p_&KI=}p4Orm2DZ@G{^(??5Jml*av{g1pe!{mMHBk<0AfCPVcWNvi#fg`}Obt%G8 z$s|efG=23)oP>)^bA8&grYV1WqZ$=HSjJhjGNm+W4EhVNeK)IluCZT#19#n+fqpdn&5-b zWsg63(ukHtQs8&}Dxxa)kAUClD66_?SNyq%iA^qBhcM<1{I&w4`@|m*5|P12b25Gv z?qn)f7ToXkgvc@eejImoK!s2qz9`}d{+tH`5ev6o4*$ykQfK34nRb)tA05t1EA{ab z%PiEL9NLK=`0+s}H>2=FPd|u?15=TU*VH7{p~{QSDLc7_DKrN-sL#iW{W|?^^|A*ivIK@b>U%wWR`PH|8vndZ-2IbU(cb zhNa)J))h&4gd{!{GHXy8PkUL=R*YZo^hJ{XGBHi7y+J!3c^BJz91lb$&wW8O@gi#! zYjK8Q*S66#Fj#3M)X>{*S89dQUMoxwfRu{PEsjzy5Lo{iHQhJ4_vAId2thh~1O7Cl ziT%(Q=Z|2{M@VMm2yJrN6Nr6=jA|*!QsPDQPhdy;maaG`Ed=xKFTwe@Te8rhq~5i% zuMpt4W7fAVpvf`1*Cs0R@CvRJ8(qOSKU<&p$_+(NV%z2G)4;MX zdU`{K{$l{#Jk8><;*g+Q=aAR_OcCOy1Y@wy=}+aCON~ppm&`>|yARj%9DjO@0_TvB zj*eV;qO-R=@>T`Z3goI&ZbM70110{a10r^4zh^x*@JB+bB1mP=PQ&vMfRh9K=ll3z zjS5Q4=<*uE!{Kh4w8=wmtKo`AQvZu);wS9jRNDJn82;~E=<%i}RC4_}KY>LN)#fFp z%}Tz6ScG1I{%a|sBxOGi74Qd0vu)d!c1=EBir?JG6mvCWM|Og4PL36o_NFIlUGM=5 z&E6QGdEaR&)1SeSc0=G2b;lyXdEy+38at}_ic^{80(ozUw{|XK$YwQ!258Wda0(4*nXJOyiE!r*cUez8o z5k;K5jI1kUO|`7Y7Jks~r{Xe3~OG=_BYi zTLciVan_n`gSxtcxvB|I+c8rABPP(*3>$bE@BP85u8uOi`55E2pDXOPv|2sOgcdMr z@#!!H@3!0>J`fB}(barjXoW_#DLi!h6CG4ER8tX1=d)%DY#+z`Xa`|e_z86u2UXsA z{$^sM%(@?N_0Ph!-&vAjH>bP}__NbBRn$yVCqlA<1h4(#uZg0Do5!3~BYE$o^x~ zN{HZ3_mTH*S$U`~7l?zm1o%JR`794GX-|OH;{g&KTv3l{u-5eLz9mo12RU1_VS*K@ zey#B@#~`+O+;gBgvJykh8@0gQYbSq2S~cbk*hi&*cTqkJ z+F8CR&$L-Do?iC32%T@~oEdxP(A6!o$r&sl#%cjmaN^8(oj%%q63o=Umg5p3QOoT9nKdV3*?aw z$9(D`+cutjAI~^L#E>63rzu6-{+#nk5y$1=1?8tu?cWGZbbJxi^{kT&8YP3id_a%6 z@Fq*2q9)l=T+vjME&N!Yh6@eANm$Hp3Skck>vo(97HU5fX+y zy&QAX#n7VF_W(K^y4ShnMEYkJ}aU=(Se?gzPXXXo9Jn( zy9+FYjqX2U8YE8HAJ7+YRqSeVi|&UOJP6&8TeLed`|noL_{fz2c?X|C9n)urbi(aP zS480@C|OYu8ML`LPt+t!#mo6*(%cHw(%H?MkhK&#PK|?%a(?5M3V`$q?y`^N2(#^X z79hRZnK+LQ7ixwmVnrtADp%jro6S}*=_lc?tHm!wU%unka4=mj4PeW)%}2PbV%mIu zmv12Faewvn10<%8x4~k+4!tv@=H4G&*QH{`>Fn#~rL4)Sx!S2~4mg6y;6+a2+}2{l z-8RnyzeNPu;27m8h}zJwq{gsQ^uUGX{6|RNIpaBLD7Rg=cU|O{Ngeeu1`kU(z% zd1?1tQ`Pn4*H|L2LNSiRo@py9r9&(u$Rh7QVgeQ17h*i<;Cz8oaNBb&7*!@80D%UTK&M z+15U1za*||wB~6fV-Jj*6u}lgkS<}ECtYE`fa9ds7Z#R3=7#etQ3=*^lola7=-H5e zHSlf^7M|8Uk(bDmM{QI{g{zpK_T|U4%lETdKhvrQpmls&0ik1c1s&9k4Yky~##xQs zvCaIOC}w_5vE(;tj6ScWGI0MBtp@$qT7pXhQAGnwIo{{x7%AxvyqBg>xz*O(((W{C z%8!sJjQ9A?Xy=JKWY6+*Zv~Ce8o#mzxx=m|EfcvNwms7W+S(@Lipah$$}EZNxD(|r zu69?rsXTu{;9_L>H zBVe}w>0lg!W^*9IeGy%ka-f_slaI_!E*g$Lv!zXSw{VpT#sx(70zDz_stNnOS)36~ z6!{6|cnA1wEaeD4nwmvcHhWxlGHw|yb0wnSt4Ghq(A z)8Q*uy?_4))!8|>!Fg((7$1F@~px)@7t5@H} zW(@Wm&3=oaSYP8B?<47GFGXUAdtQ?}u%S41cR&v(vt*(P{fa7h7aa3yFK_G(Fib%p za?07ohKVB<5%K4m_cATQ9;n6dH}0VG8Eb?*(Qm-OE4*$N;>j=R`cTe10w~=-1fBCK zc$d4QMwBE;ZAKREr%`Xnxk=ojK}^T>1vS)DNd@c1Y?3>iqVH1No&RhUACv||`bYYS zrQrsW@ynIi(neAtMWxBNp1q_L)Z~1Jj!1t?H8C83MxCPw78a)Hbc;6%h)0AKSPZ)( zuLfIMj)gEkgNTp@`hd;nxj1#Tu7zI5Udh*q-|ZF5{aHV`)MubU#p5+)V|~+qJ;a^nZZ_ zTDm`u9{jOYXrRxVJl4T(CDH zr0dWqAg(631&Q}^RvePx+gty--dkCU0sKKrij-@=p^oZI;%|d0L^&VoQB>wz^sXfL zox^zcSa7o>y2(hI?x4>Cl1@>;%b?SJpTTe2d$+Pt!SzjsEt3@YL-;xmOM={OJqLXR zu@bU}{C@H_A-%qtAyj@5jQB>q^35x-We<>ExpT#dybi;PD_f~Hfei|Rfcw!)s%Dxr z7yh#(trh1T5Sw$ZX?59j%TuM68(@*8QKF!00mI^%;v^SePj6y}fjMumH7Z)3T~ppt z4|u_?TA+kONM-ZR7lqkHCS@3u{PX@S9<_6$s7EjKnKQ^ixf?BVBqr!MAEENG#tSDI z2T~tA>ZmKcXlJ76lmVTI=RRp$+w8)Bbo&rceM{f2F%=Ip4fI+y8!p7GVi;D^` zI!o}rJQ37sNd%_iewBxv93u`e$)t^^Wnc~=msjl#pD(K_k^^rXsrl$mnuGs zWGTN)3Q{0-*VB9=&D%o9)PdEzIvS#x_%APoE~HSIZQCXiOd_$Hs$2H`9(6z|ZE2=A z2yGX)w++>og2nsW0N;vVq-6^7;PNNo))y5Jndflu=Ij@I^ud&j!C49`-i7dO#rj+s zecU0!*33Xsuz~emZm}9?kP*epePvoeTIQY8{2--LH6d|u_zbNeStU3=U@HiK1<{qg zori^vH>Ka(IfVF0VQ;V)QR23KrW*RJ7!2v3KbSY;ab)QJ8by4SdaLBlAVVQ1F~GO=nEUtf5dd98O+RL z#rmH1wiK@Mz^pN_?;tdNYhc5mA-=hF%J1R{O|hXDFLfuMV~LlI(4%&~cs^W83d39V zS-AJtDwWdJGKkC_L$Lp*)6$bvX?Q%hY{uCVRu!rLwqyHhOYC@O7fBsJgs-3AHORf7yLM)89;(n}-_V{)_ zokhepXlBX7T!j2gmO{_fk=2EdpXdb^8?Oo3v+y3S48eJoIIml*yFtxKQZ22+I;td)-)g(E+R>ZSnk|Q-83sF zJAER})ZX>PJ;aFJ_jTeN`$Ei{i4`wbfAkc-e@RjGO&Svlm`TNLH0J9%q*(ZNjE103 zJ!~~17tM~Qv+%Hwk_6$MkRdo`u~kEL+#%nzRhrwxYPTBC2Z~lyZlbl7F85pAOg+(reBFw!A3_XFIh7 zxQy_cN}pcyTwnL$W$XfW%$-*cbz^Xdf$mFj%kM9A>}hh#+_Ta@>phaLEyzGsKQBV2aNGp|2g7If3h0g&a`+T%%$lVD491X)m0<8(RId%N)(>YIUs*unv?o&dvWo3SuWz zHNcS3dknnL5q_1xF*uWqxtcKXB`xC-5_|b-wie%X^AP1q0?%pX0+z-*3-JYXOM3B1 z=(ScH7T_Y*RSJflT?B^VS3)*I2RACM-=jiay*i}c{@L;-KE=czfK*!eB~7pr?eY&# z@3AUMH2xu*?x&m*sQ#+4n~!oTCV5~;SsFEF;eyG|ZKI9vqV=ncCO~SlgPfnV;0u=f zr)p=meCR7uatcGE*I?DI%=Kl2nOdSw8NB?9GNoUbb--bCvl=Pkn58Z zJTP-;ao-`;_bvD_|Jo_#NSO%PlvR5I+T5=oUt2{h{GdhttM&>eQgS+Zje=P1EhD@1 z2PcoRJzp>;kah=Heh=q@Eq(+8y~9_F7g*n#kQqASa0g>bLrb-tmx1E6hbJVo_t#8c zK0{_EoX!9I!@{fcM^NsQ_@EMFFN2EFX>c9uq65y1>ous5UO@d4$7Eahgt>>^`@X8+ zVe=WCao0h&lHjZyUP`|%iYF*Lu^(Jl)1@;XR6y48P6C=C^zqlj_Keo%o}J4I#5u$4 z;yU}pbHgpJaM19*b&-c01?vD?O^VuYAr%w0)4Id@ueqryQlqHKSnK@V`fjw`#1F86 zoZk|*gqiRf3pVHa;cEVNs3Hcx*G==jye#6t>Zf~a2Da=05=t$p=nIB8Rza4!op(ph zn-QHU=EVWrIkCQ{OyUNdU6$ECFBp=QGwE%qP2;Rjo%!{iHi4HC z5k=X0mQc3jS^#xq)MYOi(mx&C)Of3xnbC|ao{g;>E~rQodv!T=)GX4_3d*g?)5p{S z9oR0=--&j9i9uLG;GYga^fFOgbQ*JALrKfaWCmKHP6C)Rq>Vv~H-8`tWNY#~-ECLo zPpsXwongC>gOFDm^C7Llm_WVdk2Rs1Oir$|rxcK3xp^;aq1TA0Qk#hW|MyrDz+Avohr3@&nWv5%1c7Cd~ohVQ<#N9Vl#Bu^_--5`D zrCxVV#g^rNMK7(V=hDF%Vu77xA<|eGHNZ$iidVb>*5`T80_C?^+1DY#B=N^zCYJe5d_QTF^2|C0oD%zeT8;XNnbmQM@9)f zYF^l0Ubr&r9J?i($>nl0}5D+++!~8I|fC+Q{>`B*ACYt6{7jpBj^z z$l-`0eS0xu>n+sd`X3=BDENQ`n=iO8elV9vME7(&U#y{sY$2CW!lG~!?~CbwBID+% zj?ddIO2I1h_GOml%PX&C=xXoSO$G@#EGOCfZ-ERgK|`JqT@nYDN5>g8=t~p1HLG)U z;!ivko4*Evc;lWa{}B_Yspme&b-&7p^!in3{9&+(yg_fOYHGU96Pm+-Rrb}qZ*%|$ zKtX}o)rXxTCkfW|sC?hyc5*Ff4a3$L!dzUHBfR} z-Z@3=iG^$9@oXf=ynkuC3ZYB#NtX;#&EAvSq+Q{i9A-T4R5=FfC*E>pJENC`{z+Q^k!y!VqVf8bTKRa_%= zspJ$`2+1kQu9yZhjq@<_xb+AQVqqNnM&$FDX@ASmOYIJ})Vgf`d@;2H+vzzO0jQR(9(Vk(JXh6}2l@^n6 zrO!nR07;Yi%-2asnS=Q*{I#DS&kuG1eq32n$QR~e+}q~(1T|nt9;d&=^|({I$A`%6 z!f~Lhw&KYsf7|UH(yh77#GkuXKSF{lf^W2nW#t{p_CsV&KL|A{{CmcMQ~H6aL3k1W zljeJ%xPVB<_^v>)3s!6YsQ2p!+u^fj_YvQg<*p=9b3A?xpiPj9llxEZsH3vAjtE*R@-A4t4Kfd-Q&@Ju-}-Q9Sy#$qEvJ^nOX_AUXkJ99eJUHO2VRUN4eXDatISZ!W8^x@V-hy~VOYGu$BuKls48bGx62);P>H_W4UC9%q&8(+wG=+2=K|+2{K6{L;6-fX#Q5 z=BJj)&&TQ!81(Do-z^9Sf6q&(KoM*z0yP?0y<*ZE($(CMG7*21hlja4qV0V=*%r! z_i^O^pf<4Rk^kSY+E4-$bolz$0-J}eE4CP7c4Od5uapc@SX_eaCC|p^BV&3|ycOzBn1hbwdSfH~LD>De{}B@? z^7!%R4j0LRN-nm!u-076NmU4Dp!l|UC9uj*M(puryABnuLg+Am4`76k=E0i9%z4@Z z*PP;4%8H9gg028B5pm4|D$P=zq?zD1nFAk}~vp_1*2x0uC2yUgA#& z>C)MUswRB%ARR{1v7u-O&TVH=d=@F3oTALXGZZJ@(>}RQ?;v_kof8CY79A;}ffjiX zbH>0GGE9GMp{1?19PKprHHKUbr5pUL-bZQrHUc|zunr7tVnSX~h0~|n=()c)M=XI_ zjx5GfEAv+@Yg%Nn()E2p`g@zM+PZVWGKn2A{Ke~@OEb(i)Jp#i%UpZD0&4$XOF*t+ z2%Cu|fB;!=nDFqB#&~YPMHFS+vA%Ph3p%8Vv-uZ*i8jd_P9dFah7GCw%)Pz4SgLlp zNm?;a9pf;Tt(QRK{--!=mi-@-_c(&X8g{%_kXOws`}83~`~luNgVLU>)6i*;H1W?- zYYB4}vNKC)>*)Noo@zuLU{AW-#0yTPml~kT0y>AaMuszK1pF=C*~jlfoVgWWyf;GT zj^AyjQA7AOT|P7gxG10UgT|f%7HH~g{_735tC+g%GUT@z*Wcjs+5KbSir#=}0!^6g z{IAiGl}BFH8nyq5Ns~bKf}$6a0}dY;oYqd1b)Uxsp9?OrSH^DpUsV|Tk>MP*Ob~eV zNMWA%gDzjuK2R$Bq+j3^%~rArD|}8M0Znpsz^`uC?H=?sJn-iGr}Sewy@mFSEz`3? z#zFCFm*?XeT&=Isbjh|5-&vF(n&bnQQ9zZKkjVDe0BKU z+96Eee{S4>fj8CXixloty!IK)`fs10q7EMsPX-YGJBzn+&^IeKzs~I-V{1D?WfK+< z)}CMrn8oxsGF?Lnt4nU@j}%eZ|FYikYLdL49K8>AOC?YpNQl;aKd4p@GG^H7ucZd@ z9@BiqX3G6!iu6*r$avNL0Ng{~&!Obgt-Ug84x7Pyqrf0c-E6kp$-5EG2>DY3_cmbn zUI{=WC-{5sBZLfczL!yErsk#n=j3ixLZ)5wf6pFYoCD=l7lA^~j=5_iw=V?_7E@tQ zF6vu@AIqtxwOV?VLkm>mSd5t72Sucxu)>SJj;s+d?KMw9;p(~1K|h=e7?heg<(XaW z>DZnZt<-5rgjoJCml&cMQ$5MFu|I9BfqDP_&L7WOGIk4jjk*mrfPV3{qz8yewpBR5fz1mZ%|A(HsOYWx zB-3HJ9!)6SO5cmX+%C(d^hs8O=oUON2V><67m>0I z3%3MSae}-DTqt}BPc?X_oP1Z!Ka)$mFm|E!XZWZ-fzgWj z9+3{;>&d7^bqEjS8MzWe#sT+Jvsp&&j^S@iVRbn1<=!L)rg^)-+eDg`eYch_(#$y| zVpKz2w(k1z(!*cc_~Lq{n${oSH2@a{^${o9C^FASy7EtVbE~ag6eE0=s76IPQo6a( z>~EU8`)RPCLgB}AL3J>KgyD8ZzQ!#9B4uD(e(;&3bZDu(jX0RBd!6VW^UN?VL<^nr z!!}dVhelto+V(#B14v=F00_AfwEed1;!h=Nf%HD~dDWXdnP1`gI2WR#M_3;n9tq4` zVH14vNSGYXv!*c{eWFQ`@9LSy*w9XME9u)&{D2R3DF~R3DjrJQYaQTZIwH$Pg+vUE z7OwbAF+E0&3-1kZZc8=iamIoo@DB7f#ZR7QSVSHdqaotRfaSc8faUjr6PR-lq|80~ zd-yf@?HuQv$S;o#8Jjcae9T8TcY)rnmJ_l2N5Brx+ymcRpM6>IEhcBO@Q~5~7SYx3g<*DSJ#XuhJNse|0warDdBw8i*MFPPO~y+=H?7jr9eH zWle#z<5}Ct6)3eubK{sp^AflqRaJT_cce!^RIfgGcHp=FyAEk-96SkaC;k;ZU1K8# zz`){K{)7K$GWNa`wH6(*zVKk;x}S1z zPSmxj?_~gUID=evLhp8$4cN4Yy=Mw%J**v^nZS!ot=`>R@_fL(@z|vP?r!|PfIbR_Rp;plK?x3Nz6`4P*MOPAgbqjKGGT8!~)zR76Jik z0@}in8}sfr3VPy|90{wogmGZb3-*x+yj9t18wCm*8m1(YYb-5`Y;c)P!dl$oSTM_RO!wsrCvwQd#jdvt!Db0Yaa()9n$o)2Kt4J|k?vF}#cO6JPM>N8QJzj$(fA z${v9VWB=njG|F>_N5;F{jH2>H2gDs!Ww^u(*wD~$9+`AyXNm-N{7C9(^U{ZER!w|H zR*$56x_HaE`>yF5P$xuEulU3!Mv1m#>aR@)h{@ai;Y`W}`e$I3(ImUd%Po7~SSc6; zO4GidIp@iWD5prdYp~X(Mg|OQu9Kvo`!n>ZAgDq)cBQiY6!9-+eEM3Ds87zNFQm@> z$)2ASIU9YXG7u(HHuH1IaHlx4!Nm!~1Fxxq07Z4ito;#^7pjbRr5Sd;0;Y`wdOnGyYXT8xM3n=zZJaKOIz!y<^2#_~M8D6GJpBbm@+aL^C9VTzN8 zBlpplQXdglCB~?zesuT8@i2X}Q2ebIkIhEwCc}A4;y+>nT{Ap7yz0-brPCiVcN{$@ z-C|8Dh-9BDcL1x5=?Nz5-ecGyL)FJwE{jTfM-vyaTjWdFcRuw8NUB#B!NEoOjp3S1 zE@U7c7Yo^deE*j?f%ylQI8-KHPn6zpQ#lc-$Up8`mNpfMfR`ntOkAxYtyblVX3YVU z1Xm4YSG64iS`xD;dq2Y2wNpzeNSeA+8cfx>QH`xqk?CxD0K0F+Wv2=;2qScJ_%NV+ zA!U`%#!#C2gQc#t&Ih90Qpj`g?wi#sD-kpZ{w5XJ=virwr9vEPNu@VO6XknaIea&A z67?vA=h2pxcsLiPIM46k8E%JhdVi)DsaCZs>BK=1_D6(P0J+CGif)#?*PXt%9B04X zm>nCpZTmv%eCa89+i#{t`5OR8N4?{_nTMGXyxqd?EYZDrN6kfU1xdJzMN`fBFPtFn zOShH7DwaN$y)I6d{ZU&aq;}D7Q?KAKPPm*ou{Yb?{FMt=H;2swWF9v_oZ5UKr`JW@}Rw{M|K_1 zfeC#U1??i84qO}ws=H9BA;GBjqQ;dQMKS%?_x4zL^I(=h9rw}c!z!z~6HB}2yvwGb zeuIKkWrawN5x=~;D91EzQU6MyiFZ>qz(NR+{6 zmi@|_QKn{!rT4idxnIfzA7k>^+#cfNxnO2(UB5zK*LUD^O=dB5yya)0W*0$w{I>6ct~3D?&z64u!( zm({qzoR6Xz%_L1X7mpvuDb zBc`Vd+kwL{UsY6Fgh@GSZZpy%tE${B@O@WeXqJxq2<%s&Y-wMi2~s@SlaX!|`##Uu4ql1ejWl8iGmB zi(=p>m$>WSGulKW1+xPd%KI31Eb~dL=csq>;mw(vUO0IK74PaR%ramr`Gq2Y4+e|2 zzSonpE*WsBXXml?t+@wTa8X4<@QU$~7{%Xs#G$|zngzWPzl})yeK2U29s0xa_0_Ur z`W}e?D2D<6D;qOgh#asNH?E-#=QWA^IXoA2c zObVV}ZFU(Z8e=n~BxNJ!S>rW~u-TS$QIaXtiK$T|fImR>n{l0nY%p>$NZ>rvxSSEc zq82&%y8HG{4>}0w`n2pe%K*j8TH#DE!VR?D2x$&zAqSi zfTZP~8|4xNa`Nt}DVnc4mUDn?fFG*s5&eLndO1KL=n1gIuOcK@0%H?!J?7{mY@Ql< zDcQHa;V+&|nB;Vv&wma;nw5w$&|UpX?O!XVgHJ8*0ZM}w0{2a8uJ>@a*CYK#zG_%37s9idU8a&@py1dV7j|tI!YD{b6Xqi*v zxa+K&VZW014DA$Ug0+4RjLD^-PQxSJ1wPS5&2m1&x?Fmz=6zV45wqt^KH6UeiGxR) z;NzpZU5#g-c@t$4K3nWOi(<_(>|+T0weGslAJUu%w7k&2WII;|?a?S;+`aI$?+Gup z4IeX!^ZITJ59b`CSN#!yB=>>Z`bf}^xsVK!#bJsVxJ&SZQeD)c@ew@ar|2^J`}Paa zA=j z>jY2nL~1{_Z%5Iun*ro-wHH~Ed`GV`agfr!qwVY66v5( zVdcYfm(DH;>qds;geVLITvYBnOwY&OJpBQC zWE94kZe2`6)IswR`8n@quw_%}E21~()&U}O-mbLV=eP4F8inBD9o*NwSR zzIs{RkFR?02h2H=rxZP2F=V?nkYcKoZSj5~5lo%6@sg2!%Nwl2*g$Qw^>%bWf;Om~ zcVpdMPYM|y{#ouTl|(`o3d)b^^Gx9Rmfsn2y*MxfWVsf$!_ki`{8)9Q&O7;PF?Rp> zTPaX5rGp`XV5tzmt8>4~dsV&DezjNVx zttbE2-FBeQ(AJ!?YMU7)hb8W{skV2I>fYZRh$>+7xw*%BvUr}J!IVeu{(Pa<_}U{r z(*_jf7i;EoNq+Nw8?f-R0;}P*aOO$Lh0)ifvWdK}5tQ|dD2`}T%bNZjJ~Y8#3#*mn z{FOdtz8$?Z4M*S#)FIi*2xw@~t*_mYu`4#$zVE7dApN;8>@ky7XqX+UNt=eE!WQP*9 zk>4(dZN|q9beniqf$4sLWZsco7*)q9+%3E9o`!dk{bszMK>ZVc-)?yg=Hzm~0*H)3 zT!(w#QIvn+myIF8CZ=a{{kWCOpx%pF}1+R3=lgXBwhMfO6j+Htp>&e&BbGv z%($bRQY_6-bVABjCW>H4AlAOe7is+RNUI2l(jdsVLW9?=353P?7eLoQH`n9ke30#| zTYQ)~LUQEUL40jAy%7+?YpErY z$GvBRLa}g;lRq^U&U-DlpJxwBF4bXZ3C*!%iLKHkQ=|r3J&zg#&#PiG%pCouzxLxY zt^HCE2`EnIv=r&cxmq)k1vvNjE;nYUe_f`2qZjy0(O5o)WsDTI+S-lNYIwkK`9m0^{;F7>_56v_J zobz(%UNjvyKHreZUT%>unIYDS^=aEs4un82j^w^pxUU-&G$&Mu&JeklSyC2dBK0Es z_8zhR-If&S>Ny5o9Wg;Nn5BQarL0o8!;3ByhH|@-sxik$ep0ai_%Cxg^2CqUahQ%y zI-ov;pB=tG{R)L>uy}dji1!)d$X?M$OT{!f9O~FOsTgpgjKLSHIZI3HOYSoMU;F&$ zf7Hnw4G4&2K}{(T)E9bujQ2XgNxy$vdhZV+ICF&|Ew{X!=reB5h$`{)uF<M-+z9IQ1Cx7p>6k_~})O!j<5a2)G$B8+6^`hqxn$=PuWMno{=D>s>o|*n8 z&9B-lKTw&BG~bt*JdiG}=F2LK?bED^o`C0{mxqZj-;yDHI%p6)pqw}zP5|^Bs@I2A z%;82LbiAo|NNSE-9vnf?Qo2I?qty{2`0`7u6<}a5y5NRDbFA?T`E7#5P3BipaDk`t z5YE(}rnMHrV)eeghb5}!&C`RMN=1lDLX2EF<_((e!6SEqnx zb>=JQTdY7!rD5oN<=CxLoL^JLs_>OxYYV}AKF}n4COfXEzx}($efKv}(K?kKMY|`W z@f_nvHcD;BjfQRDKE3b#{W&8_ND$WSt|UA4Is^)9OOo|zprQ?$W9Tn`fHGSsoIm{7 z0;W0As+OBf(UzJV1fxOdaCOLrc(qtT2$+=jp(|)G6Bhin_d0M8MLDLPmP?7t*m;xW zEh@Tc6Mzz0O5#`fzb8zRkaRaICb~6Rng3|qz;T2soy0!TL4K`Cmv3~h%VA!rgBo|O6VeH1b}dc}Sz^r^KomyZvO z>49{1C)YG00qn}-g|yw8gc~&eqmBYWEBIVYpP^9$PKBR9W5KdpwBIR(Efc;0)O3Q% zH!LhX^2l~N$`g|^Gz{p-gZ~JL5gyfXj$q?w`MkLo8uOMc6rvNGWvYw8*t5?JIYmes zkC2f2ZezVT&YXtPLZQFk%*;!onpWzU%=Q>chN?s{JE{RM)~!q4ig=XY=yO@XX5LjS z4#z`b5)z6*Py4GUcKHngq3eHQ3DmUlSSQr&mKZ5LBVE|7CBayP{}Fmd?uZ1aAGIg_ zpAOawHTOA=O*z#OeYS6D(wMbqE$^P_08d&w@+F(=P(}jfJOt~vx3t?xuhOsfaJdbN zU8h~~iyjRWC?%sK59j0^{eCAj_=Y^)I%!)9xk+_QW}q3BZ%xe{7^aUQ!HEy{jFP);%H_!AbdP;B5zRG}sd*KQTJ($~SJJ;=cA@E1fG!QL@d zZ!tsPB&|Zj47_KL$zC8rM$AT?4(vU?+LjOrn=D4>@Sg`3r{h?_7v@p`W5+nTA@Tp7u!z|XCBRyEg*BqztA{RNp!X6a{zL~$i(zfSy`v%oztoG9&)Ft^KTju` zZfz4nwY91B&r!OMNFPXN@VwzSbN_Wf^j9uSukDR;9Q9snr(oWU@$m#t}~h-`ab^h8Y$ss%F$Kb)cvPgu0Gt?^7Bg!A4ygP-NsvO=vRm zn$ONOwM%7q&5oj@5p1BWY&w(@%a>dG2o(M!92hcK>u+v zF-}YgB+ZY;UO5)XjQZ;d%CkTWAOq@0{ex@oT?RPAV1dAjWFhp%O!(uN1ApxD$WI|r z;0dryz}TT^)71s0`_@}SXtLvo3p}V_1gqY{eD)Bw*u2B04l;Q6Xdsl2ii@=eHRQoz z&kMT}#y9VsV_x~EcL?-~bNEPAya4hCFRz&TPS~`xgDSq`lUr74A!f`ueNtqQwm4HK zInm_-VslWqx6Fs8_t zlCxR%zGA_IG-w&os_Omr^Zlim@+Uf2p=0=eti4r0RnPbT4Tmo25EM9cgEUA=cXuj{ zlprbHB1oe&NOy>ImvncBbhnbvKH%@G|EtG~54RrAdGFV(y=KjtHERM|3p-hdybLv& zdVf^&5f~9DyhGCXwB1EXwN*_8OvVDRDz=Ur5pdyE$5jQdPx-Wtoy2&3qZ3E^M89|K zKsNXOMbmY}U_nw+#>HjXhpCasW3c**!Dn0x&-mP6h?iUStbYB$B=p0e_Ap|2LCkAX zD*g0rh}5QD`HlK%QBP62SDX;!EaV%9g8BW*y=JORLr_uXC1auku7v>_vkPus3!85u z`N=;@0uG(rzwGz%!8FBgQ@1y4#y68ZoZLBcFVxS}K%osg&i#h}{U?UK%ccypiWoGr zsV5YkUmOA{=zktiQ}l!6 z*_D)8udPPU<@9cb#?Ro>!B_$V!&fP=;(H^Rz=DV+z{p4eULtsZJ0K6o&@GhkGS+M+RpzGdJ6nJ}8?tYa@wpjy|7B=>$sBWJ1BRp~2zt~0$e0rvgw*J!Jab)BVt zVET%q+y=c#4%cxbnn$HzZ{4?5-}5;6rUm{;f*znH&xXcTm+7agU0M7T;^8LSNTga> zsKGTRRxB)q0;CW0)}g9@uz7NN;bC;9_v9f^DZ^qbRqT^dDSk41KMYe}retYOn`RhE(6`wF<<^Fm^ilfny?M=tt5c57gHGGysg%KM<` zO$Bjm(KzIf2>`cA)nepK?d&j)ku3YgXIc|=g}nOX&aHPL)L7n6VcgB$f7ApbJO@Eg z+>dhYa65OlE&J%_5JEvaUQ4ra7<%si_Z)HsiB6kEcSO)T4e6b5a-kA0IFa_h?0{@( z0I@C9J~hdCZ})?Kxq>;SPP0vS1HG7S7#Y`< z`me%&9Lc#uT=x$Nlcu-e_&T4#OfYG$FtcBpsOnzOyjwS8c( zUoOA70G<*`yG#MOHPTpwQ>LJw$GF;tdJ!qENxV6w0GNwo-;(QJwLdDMmVO0Q+F_{G z^xY?6@wNo5YljdnGg@>NGF7=r*Q@BzS0knD`!gZ@D*0;9tX=n2F6rkH>JfwY+n6H= zrQde2T-Lxoo|?Rz3wl?ai*aaJUizFeLiwoOX782eo5V;V5UE6~`ocmn=jR zq6q|Y`qM-3hx2Hpk|wk3Vp^)-yWJgjw#^8jFM-gDKly{W8;Rl;9~~XZ?tbTU(Jev( zx!b?9)-)%Y*HLT(0UyQe`rVO}iBGoT*im5D5`~miOdzqy?nxTg8}y^nj+G#FXMKKKCY^C zVdnbie!vq7;647_!CH?>$#=17qC)#>t~QnjwnGjz;>Q;zHBxs+$P=Prj}1R1L!Tgk z;agg4PMIVAv~DptNNGDtf*bD2+iL=O3w~?bfEK=atZ=S}J(|Z?d`FZI)GNgqEEC4` z{aWLA{<<_CSgmg~Oh<^&z>;1q#dOR{kn3X@JtoC`{jJk;VFpY~#q*DvtT{gT#bo6) znk?cNlYc$hA}3zoEJcbuFt%dlOL9xBf~5L?Yx>wEUo)WogpA%ifJca-KLdfF4Ej?X z{v&YXI>?Kj-wLQm8bzx9F-&&e*T;?Q$JrRwV=9TAT}-G2foxO!r+B7~Eb+i5S=?(}rx;^Y%v zjav}pA0aqle%W0)36x~!Z?LOO?#laTW&Tp2e`{RV^7T{JhB0Ap` zAKHW%;q-$#^CI=W%g}hwgy~M^wj%S_hGpM$fKDUKlz z_&hQHedy7URFH3iz1X-O3sll1#zfV-(K{KdWG4|%`ahDil~cPgS&_|!ztG#S6_ zd(H;eq~V#(%6$>LqRc-6mN1+W>8!ni^G>C5(9`_;p1#h_33=MgEeMU*{Y0|UK#e$z zNw9YgM{S0^!0}36S3xI#me>c~BDHE9d%??FG=ahrh@`-4PYk$K3C`kj3c_uUywiG~ zV?(Q&3L_?7?nfWvKQurj{q_b^@9Sbmd2$xG!?i!^3Fio5r${@Pz%twEv!y7;RPyr! zHB!)A3%OJ)TU@9UDzmDuoLy6%hSVh^{!#%eOQPG{LGnmZqaCNbTw>Itrj*C;CXuKK z^ztX^vH9d2W&4Xiy4j~eG=TVB0Q3uU-*Y{q67|Bg*P^p_nldK{$k*TIKIxMk0KRGx7R=k7>0EPZI2u36!( zKa>yq%hCr;gHsUjofcxd*I)DIc^YKI|Manld9Aqpp5DL0h9wLr`j}OZZ4zN8UHkP& zMM#w2J=t>PZlCrO`>Ln!+n<|@kWF4fL@k{9S+d~gFD{=Y%H-nfA#Jj6N=)`#?$lk& zMqrmio-k~Kb9KZuK!8?<#b!vX3s%uqif)yaoi8Q;`H7L~z{fv6>XEqTc#}x!9Sfl{ ze##?1oFDd*Q=5FZb+2JOI=f&U1b`koP`b^6&{Es=KkoL<=>$_5o6oIdE`uA}TuJ;# zxz0=gNkwh7+h#|TmIAIWF z>TncxB^o;^%jV95H=xgpT;UGUv)HVWUD7UOt6d-;e0V_AC`zJg#VsSYlkvO|83p>8 z14n)#CyJ|=?MI8ARc=r~)3B?h;LjCgS06dkB_=x%BlXJ~eZSAY&F+*ONnNP0Z~^=Q zI27LdCQeP|XY&iGs#3tqj=%8fhyBh#5F~EY`VkkiTyU$M|+hDdfez zERLawjf4d28A8PyJof&NB+rbTeO^)(?8qROd`@wczBO~r6kv0gfpge$Z}5f^xS0kw zfImnWKF5ZQ)gnaWu{>45(F3`mpAa|(P|Ip#mmj||lGCBi;<-&Z`{JSi z^uxD6I~7u@-J89nj97l7Wwg8j)yVEgHL~-GW`KM7yEqWB{F5e7JNlnE5kpoq@TaLfPTsd*S>7I!$D3A6c>LYhutcUj)U)-_ zU@Q{TebVTB$oMVp%;#+kt~(B*oH42U^8Vj;Ks#Y&?3G3RH5hFg-Vf8XIWK|h2wnzD z{_Volc#7APE#RQ_{@klZbv@^&Qqq~RZ@;s{Z8htMERdEoNc#{1hOX#SKdW-? zS)zr*a#)K9GYmpEhc#zdm%~-JSj1u*zTfLV^t3yRHZV$@4Nf(cCnx4kd6I@ZUdonU z=B6B>=Xxg&d9nx7bmI4)VKf(ap@5;hEY}^Suu)>y)&Xx_!tK}vw&Jn&0lj6Bmgd_FhP$;vpg#>b9(e|mpf`?np8y|T*U z>yfuJufz^AK5&ra)K?f5sX+P!FoDRmHs+NhOCjxy z#Zs2~POlzx4?q*&5MxC_%ulg}VnI)a&(SbP_S_jO(56%N&8`x zH;*T|OM_JQy0iWw-s_UYWX$X4=(@m4+ zi4>tko{CN->~|w*yWrED-%nK!k~l3LEu3fK-5k%>_@Bu-WXUSprFWF2DwX11e(y$q z3-p?P{!us9Tde8ZIHz=&w6oCpuV&_?ddx#vBL}(}RP1^vfO#T8yv*$qv0-6w8$51e zFTSEdwwEl6gKLyy;suum;mIkT_11U_f^i>;~VUg#0(i^vxkH#}~* z?+WwV4(0@1bMvo4ri;2By=n9z+ZKlgsqce5>+kRg-gxM{m;AlfW~7amssdZ28d|tnpG{u0;fa`2^)@HyAt;a1n4yqa2X|7 zL3DP_oyO;b(i?fPI`gS0YYIg!O$QTJnm_S@XUy&#$xz`oy5WAYGq|CcT&aZNb5+zB zOKhTWoF@Trm{9qhqAj!sdaK4;ir&s)M(6T8>5TF3VV)t+jp6A(GXO~&7&~KIw35b- z08oOGX?R~zBt>C+!@%v#*p&5(+OLYFb_nz~lhAK3Q2W@SA0HoGfk$BKK##etJTfP3w*E6s?PVU#oGMk?3mX9}Md6D6HIl>{pdUUVa`bks zqO-v)`1v>;r-)Vu@CFQBZyGU4;_x;G?7CYPnYGv{!i!16j zarz^a@nK6lY7j}84Z>^5S21v*K|v@oR-k~T(Uo6j2Hxx@2Vn`h9Lq_6Bq@hC$@BT` z)b!H!_QhZ8h;m@dD5q(5f{e6@7Q`sqfHhyq!lO`J+#)FHC!FJrp%;o`@A&M>55Elw5Dc1ZGF(r#4ZxxQTF z$nap#GkxF`1e>}+=XpXL3j#uXu@Y+PAi?8c4zXJ;SY>iYyh!V9I{*_F?)nBVQYi-Q zgtf*G;>`oof=^ek#g>Y1VVL{8?)bSOnxI7`3?RRI|1u0@J}LPj?_z3gjLnCf`9jy% z2~qQ}mth@W*Gz-cF;p)rR{Yn|ZB)^#NN0@ci!obM#{w232k>9|iw|6Fdb~V^xLA1K zLY=2-5ZB>w^(oOZXb3Y@vE=Ku0mV2;MAxRVT_owDPl-*u`bxB!5VBvk8cII9SX>Iw z{_-4PYUms(Qvl1JD6g^rBTZk$kTpGw-7Bo2n^apaNi@op0nwixrCmmg;>mzV?0-`J z?3XT*;>kF@c{eu-(O0Hb#Uy6PTH(R+xYgnh<&9Q{8-Ktbi%#m#9j}MfP43kl2%y4;5A5|xm6Ag zF`D36IKTL`(1DU^0u!BgEyM@Qr4I>LetX5k&&na~XIBchZb)(qIv3@V5^E9tUeDj1 zeOW>u>_|8Rky^e-X}cc6rzCT>^Sm;v>JcJda>SJCba?!J^mze%hsZx_s<2tWGF9|K zWW}H_GEOvPtL;~#_+Eq|N(DPHqp*i)4_P<=*7V_%HQ5Eago9B*Iu^~`G5%QPVpmNc z3$=kYs-!8me<={TU9u>S<0$AXv_Gn;cqz_VtNja){aQO&Ca5h)_O86)A2lV>+c^t* zy<~lDBxvwX9(sPoJ9V95m}7RMGiGu6!fxU(b6S%AkOMCp86xt;v!pmTp6t_SOvrD*JGr3faZz4>1z2|F)~NZe2H^5v9Z6dy9KoH`Pti6yl1 zpfwK55sWZS=6xl~Z=zQt10@niqx0%&cp8b93+*kOOT9a}JeV(Ps!)3A`MiM>44e3r z?@Xh*RM?Mf)RCq5Rb%g}$lkaA8aibUc`k0S38*J|#kqkM1JhOl^NTwbEf#cRw%w)|658 zX#>U@>qEik9AZ?V7GqAb6J{qH5mpi`gRrW22_SDnG`3vl@~nxKD`BGvElaz`*)fNa zC*{CuJ#crm%Q;u`A2sQDJLUL4dCRtgjZGA$uxdT4Lik*hFR!hvK#$r~wEupk{##Rf zA^Mj_ft8YQ4$GT}IFk12~&ynN@NfN zcyJno?|8htC#A}l8s57xl?BPe6$KoeyNGvh5AlEIDWrx4O1C;PmB3iK+^3%wY<*}C+b+q0;O0ra8+o4ZPEw-Y{V`Edd=$Q3WIBsKe@?93GlXCJ| z;Qm8N=EDlzjT^NiyADo7AXsr9UnCAE0=zG2bYht2#B=Gf+1HB2B*K3y= z;|x>e3laeY?aM4Y4BChW^)LS@DN5OkUhF5FD;4aEA)!Q5IHAYVCgi@2b9Z%1Cj-8| z_vPWgjp=T|cauv&oz15=RMKlx;yXr*b8bKDg}?U?cVymWGoZM_tgatgM>p8QLL{5s z$7|vy^E%z?>d_Rsghh@i={a%vKWg&y3FOUxQgfNk^SYG`^&2t%^ry_(-RTX!xPXM_ zA3R17Ouscnj%?DNqCeu%-Y)0LEZknb5mp!;<6ldEYMw0fKDiCpV{-oZ{;ev?kpKbi z`!#Iy+C;N%;aWy}27ZC_KwsGd9`b+Gl+Q$AJk|C}sMag|pe$omXUtcnAa3d#;;qJe zuz|;Czdu7K%$L6YeA}kaVTA34TE-{Q1ZHbnedobhdnja^IjRt9pt%EM(S@3UlBq45 zpc7J>(Rf{TTPJbdS;p-8TxG?ovdlk9avyJEz@7GP*ySdp9qGWqVQD6-YJ9`jT(!gy zwITlU!yieg$gF{kQiHgR;PK=hLVZ2QM!teftX(rzP6YDx*=pdxm;Tb0*BBAheCd>k zACx$7`ci^&Ep)7IIWT92Mw#Mk148K z0rGa?eq0AL?>;cI>{O{&{rg$5vI0NHd|X5%E4KAIAmAZVC`ceL>02#)Eo^ z1jY-Cc6v`I6}a5x)o-}z{ee?>{~sll!kclAS7{DzO2Y2(6=l`3$GNzaee}1}4BUcO zceDAsk9ur?;47{o0c-PYrp^8#3s_aN+&9qccb)FLNXOb<6^DRp!ZL>Srxp_ZrgKb` zR&~`qT$;0Koymg{)KR*yeW%&YE* zTgLn;IOK9ivMJT`k|)pyexUblG~YjJT5D4PTR;7<;^M*RvLAnk5j<%mI+L2?ZvKR& zvA#a&zKP9mO&yZdS`nCCiSdrPcg3;E3a@`q!}cq?E1I+S`!!m95do3%CBQqi&YUvq z4GRd5PI9D)3a$T`j?NHEd++9?_!ZUjA2s=gEk$QKe%#4YrG0{-dPH5>s@M4{JLo`0T+;M{_X~D0Ls!N&Fjg zgY9@VrAqHRM?Oq+m`9bYnqP!ODEsQ2aTsIGwa;T=4M?D#$K2!Bdi4q`gIBTWoG*kk@jNfUB6WosU@tNa$K*b)3T16Jr%txZhN zW``+zS$_o!&i`dixt)8Kc{^?B!4a75c&@n$m(TNY{1`ze{4GTA!iI zQGPH}wX9SXf_Ghrhq%#Wjr$gMbt%k-)prxAoxnZrSyOV1yjdJ%IMKKvurXs|lnRE5 zkXZG>G}S%O+11)-5^=CVIXX;dh7518p9+b%*xK&Vi~0yL+JZkLpU%d4J?z?2T8K9*oZh{OJ*%arrV@cufAcs3(DwoimJ5a=Vp~2J(B=R1fG7% z@wGL#bkvA>U);DPLuQ9&gkeZq|5X+p{$nmk@jsF@`#(t{oKh7tdaLfug`$}|%yAqs z?_xMF9FI0>=OlmPwkEm(B{g|DJ8+sdT~agNezM()_$zTjw?zH49`$>-&nYeWTmK_T zbN`cMh36Tjs5Pn)YR}N>Zgeswrp5G9|5M1-S-NZ?Axn5H?4yTwNv+fNtdkz+h*HWXg2 zDiu4fMj4gRQBOVjk0dSrPm=ueOzb3$sMMd5z?Jq#UuI_T%zoUx54MGxW%)=}o?{GL z@rrUjf9omdJlK_;;IMPWWvSGZjK}NDXECq{wYP8s{*NRr0g`?@rjLsF`*FJ@W|QWm zlN9*{Cb6?chkdz~3u-zFv60&aYru_0j=d}X{md_^Tl=Tjc^-=f=i@s?N6IkjH;QS0 z%`LR~k2Ed+(UdrW6snKut+Hy~xYgb$hB%i(Y)1?eZm2juIjwa%PYm=8OJ(EtoLM?Job2-dIE>0m9CnTI=5^A#gzIqof28RxkiPlNX$5ahWJ400s+9h9El#~oeA9|u zQ}BV|3q-A@dF@&5mG#Wz9-g+l zOXy#+fdnOwhrY>zp`Fh~m-cP!ujBbql6XWPYyZs=gYV*t-RS}UktF9om?E2s_ghfPn)*bkQ<(va-WQl<-N^S?@66#18y<4!ugOMr?Au_b6q8>jyE=_twOO~qOEv3 zDuwVUZ;_NYN+>#PvOOKwR=b8)@^z=fy zum2a4-2PyyTieX8^-LomDgY&%W|@rPz_cQ#EJ(FPVKF$p?} z>A)=rF0UbvOV7|$Y1@) z-NRM#DVtjeOfax#uCfCYcf|SA3cETsmG7XQ$2n} zLRq`kC4R)x|Rl{D(Q0b zK6-|HqS0QIBy{DN?gXkDu3nquS@iCt$)Bwl=(?}(3EFKH*P}2~<&T%2CpnZS2rdc} z2gIvCM-IrUSBL!2HIxL4C{TZgL9O8pNnT-W0Hoe}n01glhlR(Lyqf0agZ_LoL2&p{Y{7^uFz(d?^%%6Q zg)$gsVBg~;EK%+8lLP8ES*P_wa&GFT7LC6@t?4@1c4a5?3(`wro;){_n%g z3XJhXpAy^0{wfdxZGA8M*^INY=B;9=(kMu%JT9$Oq~jt7tz|IcQi;*!0~pl*O)dL` zd(S2jQWwtW5K^ef z|9ZbDI$HKwHLs-#D2xTc)w^@^#b3NySB*@jVaYpHN-HIMEk`GuFXMEK0XmO_JfHPp zV57CQ*+z`+$@tdt0?r5;{c;@!>K!%db~a+LWaM9(BG|p9$JjY(%a*|cF{h5|!HJn0 zy$-x9uaD!z$jF0YfKv_~FD1IV;|`cToICmokEfID!mjl_xmJ2TUyi_#R4f6SK5$0S zdGFXWqvu8mOxqN>aSN1M`;(5!T%lwm#C>{o-xda%z5U@h55QdaYB3HT#KEyI4N~Xi z-q=%47R>+O3J|(%>Wi=9aKwHME|uA4*+no0tyv&(s0X#qXE6cviZ&vM`rjt3f1M7c zT36obu1>u5bxQogKl?eH$Y7@?^rwI}AGtMP)vc3n&&|5Swq$X-rXU)5wkYqxgty|j5|ZemsoI=XpsV&7m1S!rEtkqm z)MA~&>wTvOn!qsc#70(k2VL{#WpCS)^U_)CHwI9Pd-6P)=zIt5kl|XvFtci2dZ~|8 z&}sx)gDEoLJ9_Qr&UyCP9pY`rJ|=eS-uyOUDD7AOt7(BK+WD5h{dtO%aG zEGrM)@G#b44ej?~fZ+m~xEWn8^?!ZcXB{RpsVJwzYPubokX7vX9_smvZyO1txevhG z-#6GZY4`;YC(XB!5zB?l`tve7hHq+-i{x*jAF#9m9Zp^Gm9d7zDxEs@=Q3Es36t{# zY8}Sr;ATwZ9E<4b`M{vvCcj6Uo!6&C0dWgwGL`LnA>Z07Ic!5x)6VXeWOR#u1ONMy zJ24wNY+MH0Rh-F#47C?1T(2>Xfj+POq7MgD0N{@n!knaT{7hIJ(vw2avh{fUs(t?e zZw#o{=WZlseJoe9hn0lgLXqCT>dz>sfS&TMXC~}ONy=&P?% z55QkEynuykZ$5%uO9mx7n8~i2)Y}3HB*(v#@2~4zdOA z2nnv2j%Ou7wb)NHmhy{h?e-bo#8%J22JUJdHcNhO*34?CsU=q3N17&@ z!;h_3=-cQsQdf5xL3jNd_z=|+1jPNkxWG~66dKYx#TQk=0{_K~wxWF4KDcn)H{$*i z0Pu>K`5;_!>TD!JJ2`VE5~$tT3zfWXMDPi zEhTF9fPBku;L2Jh7hiQ=9SaZ3ktywb)O)<;Oe$&}U+HF?+Vd&O)($vZ^W~r(6Hg`Q z@FK^4tZA;VL>aMLmW=jHpjPm4P0S|I18~vzbw||%@#?X-qrnbC{@5mPE2FAJdp|#e zuQJO#Qnf&&jsgkRM3l3uY4#2j#}+u7iiOY}VUGyW*cyoF1T#sp?tz2egrJ{gmc4oN zb8^|cpKtqdnmP&l)y|hn`QTwXHb=L4Fi6ME^hTzIjWra7S;^W_6`B!RO;B@4Ms1Ra zi?yZ`xfBee19vCCXmPEc7Ve?(9-p%GZSJAEwA|;r92eO4-MY0Nzk%P%J{+3?a0Vc> z`L70aF|#3ck5Hfv?C!Y}Sp`+fLtO8}fb=wEwi8VX%ahi8_=1y5?o5n@^4GCjXxzs> z+m>AYDKoG*drT452I&q(I)N(s>tFP!Xj;ZXSO4czcrFVVG>qvBKk6MAJ^ENgV?>FuJn?6_=Jn5! z9D?KU@%8$b{}+acJhU~qxA5K%Hxz4gNxn9C(Osi=E(U@z4nUak#Dnx6IWQyyQDs?* zNBkJ0nO&U6Oh2KfK6GCHv2Y=bIW~jLfNHJ`b}fM!p}eV47Y!%zYKl67gTNvfZGFhM z{I+oVHNo*f90eb3*>ApzAuBVJGJ5=FA*kR0nR0X`H4sw3vas=Y-USRk<_yKs4>0qZZ{&!{#++K+bHipiy z*pr}5qmGFm74f*%#4t6s^$W_H6C8LpEx{mx zGxD|bCkRhg%1S4%y(y^_*uwVD8Tn;MxGiW56^ow#wr~tmynCFGKsxn{+FH$?M=OgK zJgbL&?E;ve4==coE-&x@fFt_77Ub;RNl1F-Hcb&P1g$@~mHP*H2qo?KbSfHYtmQzkq`x-NNFcog?X}rVwZCD4l6NTBVA@LI!JYqP6Sh zWr0N?LGc|w$JsRh_x*!2F~PbFRo&;gI)Txm2H=V6nr%%rEA(FwM{H`#wkR+0Fg3&a6sTHixU-B(3ekzQS-uM#5K^nhKzgF$i~l4-_KZBrb& zzvOxmw-dOz&+_tdeGlT?ylXCYKZOre6MuPht7Z=k?g>s`t ze9(>5pCfZ<@GoRIYPe;?n8-W*Dd@vYby9)D5s?2488c{N#Y7kNJ}L_B>5~3zf8?5; z5T{qtO7=R^HB1Mb&JouSKK%Z^bW3&@Uwo4*SVb*%fjkW|2_%7fbsnckUZt|!RGykU zp#SZijJtwmYoP=1_scQEMg!qRH#e3lhDq1xZ|%J z!H6(0=63Gez0WwjM_r|HL!Ev6Ks=98q49F7Ix{-V#A4W%&c+&JX!QL|a8ua1jChTu zze?u=aF`Xb$$TjWR2dnSwI+|OQIUF@6>j12&DXt?X(J~-3&1_$J(!o1?0eCjn^@5$ zmyRNzYR0ZIQWX*^ldlk9oIy5V&7y9UMqEJYL^a2~j=+t^6S4!v zWhv9gkHOg+!c(-rG(SmBC@WALAvi&Kd;Xfe2M*(~{A~ zC7dCvFhR8n(dzR#?>&0zdGK-mSK8P3pDb5j2=4nKr}>_p-5^yPvPw$-$09@2#&2Dl5;lvS3eL_9Dz`v5%sAm3e*k`;nBP~~I^@gHej<=$iKOZMsmwX@SwiF2 zpd1h>_-Cl5BIq686Z>zIs}5NCL${=CDA(95=c79W1B?B?7d{*wshLqHC!G_oxa$1O z2V>JHmd1>Tj69h`)08AW3;}5+r*zel7Y>-c$^-Rwu4dyBT$sDP`!fDK24Ap^Q(7HS z9)N2aROfS&_T%vrt%yR|Mp=Lp6IKf8jcoSEMwz~)CdYtbdiI6N!e%@ly?l}wrHgRE zo$i7-9H}(6WLG-mgUEBy3I-{hIbSKt_W0(!*UQVIhdqd?$yD3h>7^Suh*9WvCKUSZ zQY5Kn?{PwWxZzc2iXy!nJja3M8)eeSqPi!m?ZJhxR`2ffQvmQ%MAHeQyjNN2&&(>p zi%IJ^{6o!uEWGmh9B)?yOJX~JgB@(In5F>_ORvj3IvznN{`;G^`uD9*{x1$E`IwMP zIl7i#>6M8(6enKW0)FSuTOexsT~1_y?^qhhDp?~w32*b;`>|I2D(!U_;R_BNHz^7v z$)Qt>(`eY3mIog$P?I5$-2yuqkF5B4a1TzQQ$nlG zQ7-VwQ}M?yqq+%ICXzZ{xyuu;4h;+kd8ays$vI1&oHXD zZCgd&TVgzp;ReHih8K)8%87lu2(Nw)%=J)Lm`%}Z=%R5mnbeejgeeyTZka@Q zwPkRjLC3vEfq41IR|zjpI4E4QDwbD%h(@#xlp*o_0j};f@hUg@B4Tj@-VN=M>}B`* zMj1Yy>Q;W|=*R`M^)@6y@Ef?ESeUgc?JC=VRufW1y)W{op>T18)T7Hyg?f7UN-dzd z#G^7QS+{(ey<@AR=L5cdNZ6mQ_SH_rH&fpC1tA5Ln?C@@bkXQ3Uae;@lU3NsFjl5O)!p`E17MvnoHY_QRwJ zWBuu^ z0b=t;uF?hFTsmOx-C zbJ72R!#l#5;zVK2a;o&7g-db{>{Lqy1Q+U7aQvTzZz3=>d^!6`f{V&gOPY`n=t}KX z_6In8dSIGqx^((0!bNGtBAX*+CEQA6=$kZXkHAfKdxDg|fUCU}JFpp$&D@&g_ZQge zIuyl^jFlKYzfO(_if>P8c?sMad^RD6P1Xt%v~uZx!yD3!ZOd-HBbX-1o2TMBVNP=L z037}6;UcaInqW3-sgC~@E3;iNSmR&-9RZe=q~!)@6*v-L6`lVKhwL#kaB=ptG=<2j zZ_CbYocZqxABC8`dVQie@L=Kd-xob?N!nyaes?O$R_Fp zgF=nkVxKEyCCTTGdt6={((3Eu_>)HFUK{J1M8V)xhk#*}ZZa2Qgy7#qv3-&bxr57i zz5TPv%()I(B0nIHqJQM&Z{Uc|`2Pd0i~qTPT+kz4i9fYGD^xWjqspu)xG*OF>;L(1 zMDi~O82Zm=i!w~q$*?tVez+mN`UBj1Oaw)!pS+AkTE0lg%0y-d`}_QHh(Vd3?%06b zQozz*z-`}%pD2+%sp9=ebjl#e&+q)M@$N&_YM(wxRrvMf)G&~L{cblVT-)=`&)=*< z9AtLYKe_alUo0h37nE_C6ekgM%h2rL3Kgf-mO%BoJxNX!sb@yhYBgPS#dCY1-NSA@2R8tA8-w<)OXuzI~>*B zs25Y_^1g}bOTf|{ryb+}vV|K!iCXX8>7XFj^J43eHLyAAM%W+VVwpkhClX zKFgGNvk}=&&~J9Ne6k|?dBf_I1#*hvw@bm2%a+qn3slq=%pJlv<2|J@ZS60A{W7VA zDNh2dO7jauN@{-FjXimIbo9aj&DUj^Z~f>UAvMGB6Th2 ztO0+z^YEu7L5dOf(l;$t24hb`bQUmy_K0kQxSQg|xt(Z*J`GB%!f4FCJpnEUYA@T# ztu?~9Y#t^}jnIx7Rtz_T$i0?!c!PmaxJSGF#jQmRXq-?ToP{JlU>G;~&AfGdE1DD- zv$rnIrBqycJlOl+Sn{2aX(wOUN&5jeh=@u!e0;3+&EEoRd0SjdIzbSok!p4GG*Iaf zc4emM=WpQk5%(?XAs+f;pp8TqUq#ND0m3RLfhbqMOWIOfmT z1D;SKJe;uXkII6RpsmG%0?Syd6=C2NxeQTfRGyEyGtw|&GSCj&C zqA78(D{J!6E#$y-S%tWj*5g-q=8xu8RAyyi0}fyx?Pbh|u=5Kz_tU^dbnPnQwWw!V z#L5NJ6l*oH_3x48)E|Ir^IyBV&{f9<{mhG%&`Vy|BYCj!_n*`reHrVg@cABfQ;Dg*OZ<&w zQh?5gC+uSj6OOxFJg|;5z97UNTFu;>)ob~&gVZu$k4JJm5j$g=b}Y#z&^!)?`N^+5 zP!`QjL{A>{l~u_7gzP2L8+N*u_fe?Y$T!HqiTZ~v9l^?n(|rK&v>PMsMZ_+%^h`tJ zZ<%izEw?SSf*c_wBfuXdig_QC#X!`PSipXZxW1zyE+C#F6Z98waDO7wdvO6@*5zt6 zd`~I8Vum{GmozD+%~K0K70*ZzAYb#_!Yz*({YW#*`bbDG&K@&ldcRz^fEjM7{#&EVjsqg~Iup4`YO-UPB45n+PI6@pah|9pU`|pAW&4KlwCDo;0l4Wq{T;~_%4f}a zaEyo12qJD{MR5`z4YM4}4-Yk@{@kDkPk$UNDV;dq)D3nNCUU^ML1M5902AC<=rCUuV8^-nE;)A(6Fe zztqNR04~7j{R=qp)6YS5)_pvAhr}T;N>AOJ93pwcqDBV4W{pR0Unwp8<--YFlP9`f zAj2i!7UHEembNahyy!J(5MWMfYJK@d|k$RC&>yuH;btyB=O=~PDOi2;KQx+XSDNmdSC z9&}^8I@rD?W5GMZq`gtiAvd_%y*9%5ZQ*^P4`CqyygPc>UwQni*qZw*-1A?atFaaa zo&S$ZG09IDCy%lgxkTJVp^$x~glWZ-^%wAv1*;d=JEW7+KM)?O8=#(li`iZ2qGP+n}!TWSU4YVVBM1IS>!3MM+bXT8(VS;v6qodfk^;KGL=%=4d7$JnSnQg)b8-!Jq z9deI%UV=d^6~yuxT_<8@__IDaqbhyEbs2f66FunTp5>*(kJj#f1BWGIhrH?#A8wV# zj?lKc{3hwNIMIFIx>iF^BO)jb>wd<(cPUq&Twd;Cvg;f;lo!Rwe@G#;88rI~xQkF9 z#?2ib8Iw;cg^i{-iD4Db&miNAZ}UAfYb8rqQ-1;18c)#vc9)(nzwO8x!1V2^#b7Mq z)z))P8u0CNE~;UmiiMia-Bgo)3CB0KyL(^TJfh2sAb0A5z-^`!X(U`PS@Hom+Z`Q8 zg#9if9<66j@2g3ZTu;d`l_+KFE5y)9*Zp{)Pmx}nM6)z@CqJ@7(DP&1LSB<9@#MMC zA44^R%c=ujCBY9C{?h>0=$EX#qSUuvvlMU?KainfGLzUz#GMk#P`cTBZox2I4#A;M zBJjvG+qc5SU(~a_d8LhpxB*=Nv)=afimsv$4CB{36c%eUC}W{E!4HS<#;I6DPm4mT zJ>&~?jOD={;r?&nzpU>M1m6SYoiXq2)b;BA{Ny=YhJ#4RS@F}giFS>>dNf-@9 zoaDE<`AnDZ;v@#6wKMOXss2PMQ`k!LXjR4dpHBUf1<@Syc9YRT8cl9NubALqgm_>W z{{n7mB&Vq@RT^1|i%7pC674p2p2h9*F^pTA)NsEm)Bw0(7iP8A8wLIARxe^OCHKm= zyC1u0;&A8Px|QW&Xjk;()(^lzRtL+AMVh_r8Qz8Ok&Irg=9E;}Q07q>e5k;x!fOSZ zt`IQE(dg*Sf3uP!wm+4(Qu{C(f6H62IyV}}(ZJhr1odFyQ|n#EHSIwS+$clN{aP_? zQ|{SUF@BhqDKU`~!E(=mDu+k&Cz$PR+hZ^ul2zOkXfga*82!LOO7{tdi*9r@nEAwD{Tn+iGMV!F{5 zA*AGBG=U{TNmUB~zIlIF00cOkPD1Dv=Uo{kGrh3t@y3fEFSFwR0O#6|f2oeNVjz|@ zO55=@>!oU}>oe*5!Q-uw>5GB9Hy?^y#y3_F|=b6i)`Zdi?xQO|uXHE~mZA8Xh61dvE zoRiv`6Uw^MyU?g9=EFyN;!rFc+EIbom*D`t!&OFl@Kx>OPj`qHRDFENHJ%hwLAHP818>IHNa8Z#!=m;aBk zvkHi^+rlu?-Q9>F-O^IhCEeZKT@uob(%m7_-7OtTBPk)>jc~p}&##wUJ2KC_d#|-? z#Rl-2@tQM=l&|DhqQ{07=5>Q0{e;-bnj~0xS zXoZD7HNVC=2<2+&d#U(W@N{((@Xs~DCvi#K;z*~f4B^v7Tp3iK(XQRb?NncWc>B)` zr~6zZc|N(;hJNn!^JATs=#?^M=_F656}CN1y-Y}sJ2=*yv21HxOQn#*MH6cSf{PB#r}NT3X< zX&4EUXnQt$FkK&F^A)aVibE>j0A)R`Z}74-1%@K{ZFXhX!8pH&XAb$kKms$F#nt3DsQj^DE5??4!@An ztP9545^bH>08Odft{y)D%LwN;x2Jot+gr-uiy%|7mR2`l z!U!V>1d$4s!yF{-M2yura?XT`W|S?-SM}2wRV3naFa5-2efL{%9N9ZcuO#xLmMv^) z6MBhXkaX{&`%9smW}Gv)t!G{W+roFRP|l3?g=o3Jxn;j;1ssl?9P`urm4>o*O@j|5 zRmA_E;n{0YP$W91Zj{Lx0bM(RquR*{Ip0zCr20n0Y#rl@3CSR&8Y8vaoKqQwRc3Pn z%CaXh=s9P@w{rYt&#TeFIDK`2Peg0tG)Dz?D*rI;Nv4S@@tG9FdqI(+Y7@QXMT%oe z_DArJsNWtM!07}BzZ2ZtFcRb`$HUXuaUmqH&a7=m0%#KGukU60T1ih9Bjt1c$wchz%vq;;kOCL&4Q6%9X%HH|Kn0@{D<71 zg>~v0L*hT1XJ@4JL83c~%5|5Mw@nowrE~-GE-Yoh-+6u1T{?aSMr9Zh#|%ycZJ-=< zYno!pAvJc8jQ(42fw1%-s|ak7F5M8+x_}fuVKbMrj2)2aC=mOAMDl<-g*W3#`lkDqkjY^h5qfs0Xat=c$Z=&{NR^upm9^ep3Q|C zZQuk)?EStg1P#c`fm{8l|03;hz z8{ZBL+V$F95ONk%d zp+Ai6XCz?g|0Jli3GxB5@PutPxBa$Wexg$JI%gETiG5d|1h;P6$<>LjGuUaDl^g7ev;RIwQR*jM}} zgT^YY7GSCERz1p!gs}T+iy|+1{|KJ#y$15FAUXS=Bc&Jz?J1hY;|XML>o@TVcSN~NfC&ED1-x-eB*Spaaz*U0(rn?u9&g>P1shBfq z`VqIK+%Xe5copyC6~gb3WW9;cd#_>1C)SCc`Tj)OtzEE0QfWT{42S%-xG5f^^NP{H!`~95$t?*3#$fEDd*_)^qbf{IC6trl|M#{WhQ%Ev+F(w47kc zF_&q4G$+c2vA23)D>W!sbJ6a6F9j(g*s37xr}DNl>bqkYb^p$W1WNvUxUmoxtHcs4&hQV@o}I^MSSyI@N`945g=K$> zfJseUhwx^&q8EO{)dk;y^fD!Ah$0jM^b5Yc7*1(kb0i>y`i-6a1(It<_cMd+B>yKe zEutk_C}VOqaKtqF3keIyFbu3nwG-+@7gV93H%h^1{KM47G>G|yh~90Gr-VM&c?y=A zR%n8J3#ppPjEJ`QxDzPhZtnlKQ0a(wo7pe~TJAGBG~G@TeQxM0P3;l0mQW-4j3+WJ zE8kd$`4u&BQpdFW!v%e~DO}K~LvtF#;m-E8##^@qG5wxSxM`GMCF(fyccmwv zZ6U#t!A)U|eUINyBb~?tu6J17T6Wwm#7o2)LU?(lcHY=E-%YmOzLHU$Y11nbLFNT9OtMONw)Q zM=KB(KV#hqF1ZZMxR$;Q0uwx1Z>gTiiRJC~kL_hzjyABKc?WBMRQ=e=nkd4Oyo4vp zBs{>5_YHxOpu%Dq!d$kbxKR7F)l1K@3(BmjQxHcu>o1a7^er@*sukN;q)wLxe^J#; zdnBSB*xfeW@&1tRF;gH94`~%4W#=u2o-}gXLkuPrk1lG|>#0uaknU#yW;4_J9@&?BJmx|Wta z)e(T#@;|PckISNT@D$%NeKC$U-x)iht9;_nbAKuzP@?xeXaGSAs|DAOWM}~&bOBM` zN~{)zUj>@;-~QW)Bkl(~(&3p94n^toQat1i zrX!|$mt9OOkr~8*C!qN*oj;ZQuZ(ndoQKzR78G>yM~7*mY>sH9$nU&`Xt4A^Ym}0! z*NZ$kC8VE`WY%!vggTp>*DK>5`LU}Pb(MDuK=aJMObZ8wA3u|*c23^Ev!N@7Ztqi) zg=Z2cwzNOb*L%GtKmgp#>>Xqf#SmP_nD*U#{H9dtIybwEzhC`2HD&mx z9L|FA_yt^TUsFU*50sr8N$n&cF9}*-XiHx&7RZ2@ey<0wP>!nG&~&?`$Lxd}HT}iD zFxijoed*y|?{W>&&i{-Gq^-(b_}udq+$<%Sp_?2eIJu`-)KZpQEw5(^#ro6qg3?x> zBGZo(i!Wm-yqTS(w(8YpF@!L~A2ygiM{?B92Msj!_bB~Ef*Zxp4pcSQTru+tuHZi2 zr8MppYiDQ1LfLi;>!=Wd04H@=f}KiSr31L1dFw?LeU?$DA{5`FsUux%u&l zNTY-bt%;-UP|bm37*U!Ja?9Tx7c919<|>G&XA6t4jX_9%W>e|ns7gqvaF{tNEJsY<_g(hR4^KIm52PxQ^Ho_nIyv_qm4xRLrwtJ^54R2I zPh{Fd_r;w)*?68N^1$M+qJ-%@o!E@C9{V%b*sLZ9V-A#z`Df(3ml7DU#s6`kJEG|C{>91c-bW+)aw zKM{$;2V2^W52BQnOfg#U>U2ptFSmVF(UO!dyHIE8a^?L$NU9$DaoK2&Q!&z=@LcGD z63FgRO^uY-T7tR_-PWam8$grn-G?|jURs7E5oh54b^Aml z5)up798SHIZz(;KoNt(5?S8l}NHlfY&((iA8lTM#{AV%+F$zWz6@PcZZ;F>rCCZ(n zld(z|cejgUIxeD;A(kEmq6; zP=i`e!a|XM@2KTTG|u;L!v-3{q{^A`fRznQ<}NUdD;iW>}dWhL&Pm!X0Pl5e3X#)ogq7c zEm$4CRaSj$x3ivR#ykU2qw0dfj*H$ni>Nbt`KXVL(-T#y$)_aq<$U80)|70)T@9V$ z)@6qEVQB2!vpOLyfc3-UHPRn9(1BR{Mof=xfGSZZK0HltcuXDqLtT`}Z~`5)@eSi7 z5|B_Y{Gjgw7EY`a>pOi%VIM7#oCde$ip2H@KVHy+?o-h83ZyL56ciQd6l?UA5W`MWn{WR)Z6>`z zHmG($FZ=X@uD?R;W2A`AxhCfwIsLC_SpmYWaWEN}|BG&znJDC+zMEjy5MP^~N3wR) zyh!!gqr5O`x-QR9VGMX>Ls@S0zM%7z^PdLyY5s<@7eH~FlTa7y>r-ogib0N14g5gp zGx15-F@n=TyLxeG1C)bHZR}6|uw_*XnrchSC(SINd7ua`^_BOhMSSVSsj{q@xFt4C zthAx*VGIIH?@pas6=WCmf4;Xv5)oEh-lo==6C3-!DT8!1=WWTDY}o6HEvBw^amkm! z3AmeRkHR#J(;EU2YV&fda)YqE=9c<1Mrhc&$9umXqbD-m!8RyG`7bR;)m=Xf3rRC? zez$5dzlGbdlBalQ=_evNI^|HJ1lrYLuwjt4=tI6( zT-7V;oky6sit3ib_HoDX1d#sB%}d^V>6(wwr3%gWi&TxZ_0a)bZpcK7aJ#v*qRd%S zB|wn)NZMDZ+n=a}9qw)ym~xy<0zpV>4(E-hl=|;?-&`&%`p2G^A-pMC3dyf?m#F2Wl;0cA`Rs{E>sWd6{$5MYkON@7 z_U1A8-)2q?YG3%x_tS4))bzh(206MvGQEG79%$ui5f!Qw7*wXqr)AoF4qk8Y%}0iz zLqXX;R2vvNwgPM9OTEzJnnf{J=fq%jA?R$rEz%;NGJm2oYD6J`$wl81bs~@7x=dh8 z{MYP*CS8^}PJnMd4P$Eog>A>TW64Skz~ma6b5MR(Rn2Al^Jgc&17%hNzn-#gM*q$JO%0N*LKoEU#Y%ljqA4f8nQAZ(cLd* zNA9tPZvqS&1u?r10N?a`g4642{8XBLnMeLAs20A2IxyTr-tLoVJH*4MGgi}m@Hn6m zn!19}#l^46DqYn8@ei-~L*A`9x@f{|-0nz1EHP9YJ`t(V?uXm=Qdo6OAbS*tn}m8! z0J{~vG{k^e``b=nT>W|LUw7H(xfD=Ll|+O6+#N3NNR#Mecg2xMHj_Q+=li6vI3=J# zCZYVj3(1;SAnM%fvWCj$N&}%3YDnoir~iMSVAV3Rocf@Z#EYMTzVv)|a84wj=kKzK<)F^j6En1) z>p8u-=TP8!wm!JYWmq4B*dw37rfX6F4O)JG&jTXLe8X$MPqSnin!lWPqwFYfgL9vM zXu6MG*M}l zv8%aIDW(`+4HD+5kp(bG<$U=8JzeMMcv2>GyC zepB-L07w`T`bVa=x52r=$J0FW5=ri2w>!gnRw5~->s+0a<+&PW364O=1_XLz(IlQF zuYl!XzZnZD;Wu9X4a(jEv1MqzSxxo5X#i7-by>#Q>`#KNpdcRQ0Meo3P_Z;rg#;)x ze7f)Kr5HINCP@w=R(Pzw-tACN|BJ&eE2N8ObA`wDJJ3sl(k1Fns{bsT)jXkQ@)dK@ z@r^i*nZ=f^SLofmRC+Rko&m+9zA*uzK!MTcWU3mjgeFzp1#j0*^&VDp^ZR$Pmpd&& z^KCAAZ$ULrQ;jZmA`o>}Ct>5BzYt#a3b0GNc=hYJfP@Bow_5JgqfRxdU;)mA_r zM$XZF*nrSE#-MLnc!SDu=MG{cvN{{F`H2K%9mJbu@G`7p`}m+$^C>f2CWZ$*mm9x` zwLa+{ogcF-;*w3gP0Ind0ZBq*K-g95ADPDBQJIr4_${WzZB3vQVj){CUYhS)9X%g> zL&atj%(DVmT{{L+jUkf3#WRiTv9eo;GNP|FOEU3nygk(_)h7Wt)1<)tNp#*_q*EP1_e0SyAlvY7cinmAlM|gmncq zRQQPyw!Y7f4txi-18OTiAA z_?M>l23W&HmzRmzIRLtj>vT=Ug#+qz6qhPUOQ2(J()V!Da*RzUTm0M$H4!%v8Y zWiPpj5X1F}WEb_3vaZ0z8iJIquC*#nRDp)V5SaCe4j;H*H&ICj8yd$R3%-ZDD3m{1 z?r2Ix5^eVMPyp#fp)A^+{PW9tTs324F!Zrdyoln5NOC`QhKqqV>U)_-rr$nXhmr*E z1CziTp{`;k^+~$?oGHx>GmH0wR|o1~ZmtNXKy6r`Y6kRV$V1PjfkLO&d=Iaf+>){= z@o9{xfY$peGJK1pYIfa_$uRY$H4H(b<};Q#rcX!y?b% zAxd=Rzr#T+qBMU< zsMJ3byFD;Af7+OOpu?e7%5ebhO*H5VScs+9oaViMMA~R3^>UOEc-AivVYYoL57jiBlv4Pb}kb!)a==8!8A|=U2+rN z1?S}`R$P_r*P?Ok(j-^*Jj1vylh*4IWMe~6b#Lf8B3V|wN}ghj4{vZ4V|Z9?3`{T= zmx6)^eh9(E>AeCWl~Zm9Q3w=5T(x^AOAx-bhmxA$+hJx%KDydH>F1Ac`e)=eUoN|I z+vzVLsk``}#he!`*ps9kbaSyRp&{hd48OJq>Q)=sr{Z0FnB>R~j!vtN$m+D?K9b^G zktH&RsU*Bc1PX3}Pp37symUC@NM~_^i;D_S_*hef=0u!3D(Fio9o853*a9!(Q#NUh zT3U4F6$edyoTB@s#I>0*|E$mK08GPGaai-;~8^`%&^_mD2 z#3{zEm&+O9tRXn5DIQeR(V0Z4h7r23V3 zO1e7m`kj(FCVqL2Vf1Fjx8*iFDcxKzx8p%Fd8{Vscy?o7U3+xfe8I?|Xpb=2&NPe5y zq`r(c+DA_j4_-bCd+RUH26_g61-N(?&jXJ&Pg03RdXG2wwXX6S5+mvAh-^QHO=l(+ z5?^5D61hvo3!~1zDxoOuO1M#lCk0UXYxZPGbHWs(knrt?s&j5{rJx9z0Ru+@eY~sA zlw5bJ`!JYlP${x~A>8equhUiFd-cX$Mv>Dfp@+CmRn* zzaH{!^~Ip9>d*aE$(bVCzf%fqhoU_2$N+oF{4{v%{&}towNO?{i{3dxB{-?b%wm*V zty#zjVl0Q`toDgxwBP6A1a1@p_8df?&paTNo-%MTL3!Nh)n6(BU!5XaeL59^brBW@b_)W9lJtodULC=B4DVwHGu0o-&~C*$8gi#6L#K zzLyr;?N;CDsZiu;CRjK_Ef%xAZYm}N{h2?@L1g)HDJyMBv}$tK?N$9-p|MWiQMz@_ z=9K)BpU4B(K(!)#A7a$6Q_eoYoUy!Rg_i_&xn27m(Y8HgB)VYG#X6(FJWyco&N^dq zWC%X$Za6w!DLTegL}@p~$`2uMH_#$N2U~o1N@LRH0&K0oS`PMHO3TH z<&-AVe1kaeKTM6COn2h-<>XgzNd4)bx1J@+oD5IY&9ZixPMvGlb*cjG#Y$tFmLA@* za|;Tu2NiqX7DO2c+OMwHnmp&`6>(NHc_PzJl-X%5x_01370T8vPio$RX#)MhPmK~< z7ct|(txiu6)9;lWU!ODOCE_qPj*jN3gp$GO^g?yai2O69nR>bjcz0qTJk12IrS5fGT@dFLN-W|k{Hb2Jap4&i3$tl#-9&G z%&|@HeFs}l{flHKKUj!7Su$I_T?PAVOrH(h)D;4=78i?OieVvImKexyx87B7@H|A9 z=}(a?L$TWBEe+I2Z@S54Y>L^kfw4q@e_*CxAL^(m-jqzTK@Dt0+?7LY57O`S*QWM#GjEV&+ z9XkuxeMxM49W;$=3@tm!{P>_#~%Fu^gULZr&U9X|q;Hum>zjq&Qh6BPOFbgh|%w#Y!$a_1_ilzC4Q>YPy`dIm; zCFuAT5Pmd*nqrv`jlV%UL8&|@tyOgSltF3P+3KTCC*^?`3>S0QIo=4e&*_#idMX0* ztqPefz92X*xvECf`Djz+ zlMCBWGTR{*2L?{ck3LUeER~(`5$R`CY9|x{W=`{pi!sOCCo{CJ<3{Z=fXOK@Q!jX5 zSJ+?fl(fjW6@TapZj0z3u9`WA%NxswipPFtgwP3|IAMOFgJtcWpZQ~PJi>#C&UcL# z7)?=(AhnY5|H>pohwKX_?sz6r*7wyOhU5M8mr<)-c3>ZcD#M+4(F&9^F^JXiP|I)= zsu!s-Ha)}dKERrwoKLT*qBqQUC&E&(1V$O0FphrH>;(dOwSjR0I8b594R^Y&4@+C? z=cF?WiE0-}rlB|v0VQRtU8Nyam|P6S)ctSh#mR9_(`^I1)_9(+$1;h=dThk`@dZ^ z(m)xqQ(mv{07xS(IQ~wU8IfW3hzD|I^(J_J587}y60l7btJBu!cuOFpYcEcOhC4A1 z*%?CxMwH^J@Z1)$A=tNf6&*0#fw}60e^rwDBpvrMM}D$Ksaz%Adfcj=jZ9&oUS`pm z`jesD`5PcufZ$_h!`m9JuWe3@Mr?TXYUjR1{@u%@v@E`?XOSV0z|90Wr2n`wj?CqK zb$V6mjONF5?suVmM7*i(a=&^`zR{=9gODiMof_GZ!!S9R_?!o^!oQhHvu|*KnE}JFC;ai+<};8Uc#3MFVsi}c zGQuZdhBS(9iGaxT2JVoMCkF@Z3pm?rK&Fx!x^V+o>GT6d_mi}hr^6iu=%>?1$(dm%Gf~MOFZ6$poZ46I=ofL; z+V_?DdSxaysDn#t85#ue8=(RT(BEjmX@QYre&{ljeM{V^$6h=la$9>8{ETv6e)5jwZtY$n-6M!^7$ql65y*S`t6w&9w*CXK~gr9UJbt z>@=^HyD}g#8xE(-eue9W?J+VmXQVr>kQwxls);4b514t*epM}IVCeciA?M)vU1BbH zo&|moK6KM|xJgw&RiAnX-+iN_-ST+PH6*U?<-p6WCI`hdAi9i626ZHm0cD0U%<5hn z??#uR{|{5{R;@(i@OqB;1!2&Bu+?y4uY1Q}OGD5H3;O%Yu1g@m8MQ8zIlDU=lFGhU z|JuE*rJW_X(@Oe5;Xvz0NK;KVCBUSwPM`m=T14w1bR}X#^6Yt6g-g)Gk;P z4Q$z93)^E=vHz5q6XQ6+eD3O)1%C=oYP>YQWzDp86X>90)t_L`gu>2#5HCD1uEbdk z;Y%k80Sui{_-9y(dT?FKps%UH`!0mJHVeEteA>@^10x6ZFyBRikTyn_#c^Q` zuZ_1XFqpY~vVa<==snKFQluF5mVb?o*9u>XCwBlnz6UxprtC#zXR{%b&=%fY#{JE1 z5!}akASGzrT%g&ka}vr4a-^>ewT1(EfaMIfK1|iuZ*u$??H=fS**o0W+dKa{fCTbg72Md|PMICpN z>sb9WasxjJ%D35Nkk{f`+J~^;s7-wjKRqPtU-9mv7UsD$ z+hYg&rl1~S9BA*7R0XaV6DDBQ=XTY&5WVY$h1ZgFYfYeT7*P{?>4QzjP~UCM?@WyzBMvKnIq-{>I_IGu))nX`?r)mj#rZ$ z(}EdG_i&a32_A~Zt6oWReJ^>u|002-5OCkV*wg=_N-BtV$X=sqi4;JJtjJKF$x-3> zAx0C}I$Syn>eLQt9dMfW`u=2mBlzBLp$plG_`sy;-0@{Q6@Y{nw_J|l5-BRaxx-HG z@!@O978{ZKD{A45j>>4WoIV~966L6hJ7zCmP94B7h2M|{H3rmepYM^~D z^|Zvn{O&~!gQ4~2;)Ad=(DoW4A2);OTsf}ZJvNhfzuv_HC6HpRRgJL5K~zQBHKk^Pqtm$<5a9q-wg*I@ zfor_}zE$Vxmpzsn{OuPOoplF&OV%<`AJuV;NC&GKZ50UVqEl`uu(bX<80Iawd*9gu zOy4>rzkdnMv#s*1k{3E*e~}Q9_ZPvx`N=-7yBorl$@0r;h1-3LX5|?j1Uu(Ns|RdQ zUhZV>4r?h4@cpdvNX6~HOOSr+M_Op=FbFfnU-34$`^iXzGHf6L|H%&40lSVrZ zr|L1)7O@bDD-5Dxqg+CuI^c|y*QG-uQbWxoMn-vG+D>CP@0NqrPFbo@KjS{HX?_o4 zy27P7OwRBKRMT~Owkb_xU{yILZgqZ{xwd`=*)RAu8$kMFa#nP#>EBZYl0AIe%^GoD+aWC109uLuH<#FwoylmlJ2(8ty)owpnesR1ts zAT@JtOje4|l^@jI9AJy*B2lL{DYq^`Ny%47c|}B1f{@C{SmFA7ql(S(i8z#5wz9*(%=j!uTlfJm{5qAP4$)zA>s8{L@AbTW}Y}K z9h!DzLlNeIHlCe!1(;x?M`0;Co`+pYaOI+xi&TpL_y`W&Pqls%LBF`UW;F_8(t@Y? zp!X>P0#dvpiF9kY?#nj{VNN)USI6*^%Lh!@UH>rk&PPn3w^iWP4HDvW?3cP$ZkQjQ zm&MzqzP=H5*=g7Z*4{l5MREuu$HPI@m%+E?rCH2(YR2!3-g=p`=qs5eCjw(PRfL_w zy}=d1K7J3S&G3;{MyyphJ|Qfo5+h!4SH{=?#B?dWjTg5WJYaW0h?TpL7> zEpnNx>N&xS4Fw$Xs=`GdMs?yoheefV&ugY=YJeMf^HdB@oDem>UZ}iJ!NHjtw2=5#qTQ>$Z(eny>Q zF<`x~^8?DJTuoyWIJh%SFiy1EJ1x2W%qfTR{ZCV-iyk{J?Q#uj=qveM-UUUz2qAad!8?$DxB7V6S5|<2wP>m z-RG6?E$>CsGP^GSdIFZl_uO@Q2HSAEiHho5ktsXa;)#R#!r^mu70wf4oGC!DxsewMFcy7X#71 z$-6SH7K`zPMWx^g?aE)7EMKD#f*mN^bh)3Q^rUus_J2NY3?e6>i1> zoK)KP3#6s)`wphboI?~%9Knz%*_g%dGpnQGCO@_uO$qgQT76Vo$hdx7Fc> zWCbvn-N7%UEg#ts%tP{vJi-nfjCqZO5u5WR-<*{#;uG=Yrx$i^8bALg~u;8$5rpFXN9^ekWK2 z;-$lga%-k!UZ;Y zs?gwLTqL5a1+Ju%fs=aWh73LfdoGGe36DPCViGKaj-U?wD>s?$^ zY(ignj)3+DqgVp}FzvB1plL~{g~`sMU>r*;gWI3pWuK-)2HcgbrQk*$L;>$#*96SG z<7JzxD$HjeI^MO|xiBRrbgA%Cyyzuf(~kl@gg=GJi{%ihgMU<8Cyj;?){tA*fE)5x z9cnWUqE1h;_-tYR-(^_W{*d;S?WBOutFbl(r&*KUO}*4_$hju%@+;4y z_4hP09@WpN_&RklkxhCvpQC-<>Om1Y@XLFN|3oAVZPx+0iX;i-{yA@I%@w_#<;o)z zc$?{RY!0eAD1%fXpS|z_D(vVkBY*yMcdP-@oLOP;bdU<|6mj_q29{Q;2EN zzF9>%=Ocp)^VQi8n)vWRu;dqp_)!0>pM`3oi_LOP;#5iUn{OYmR$C&Xf4#Xp>x(PS zgyOWx0s8kczi}N95Upr~R;&r7v&@$VO1bk+N+1w4YtmJy%j2j4zp%qwcGuQ=IW1wd z`08-ro9<#*f%1ak2Q##wj^_?YKVU)YXM6LaQpvad^UcNhuOp>QopX3<6^0|uPWI(5 z5I^#b{B!?kRuBO5)-E3N+Ie=oXHdjDn_A50;}?cGn}h0a?t<_qK-l%070aOcUUmmG zifA`8@t|K{*gdeS)wDetj{|!v=n$TW)H+`mew5~a+S;LR5`eihEg{nVpq1UG%P+H$R^P6ipFO3?)aU*p5@m z&p{;E==QIcCw8zq9ArA7-vEjN4KAkn8-boI3b*E7?O*tRF?GraZ9cPvrCmYRY@dBoFTJ81 zlhXQ=6*O{w``z9pbhYD)&)ui%>8YmY<(pHCF-R~(9$_(|93*oPU)EuJmJprgb zrJjPJjmamf1inu6`0Vz$8H2J17bof&8#*q^a|%RC?7<+mXq9KUeE36bq+G#;$N?eYhw!J3dOB{=mc0I>H*Xy=h=3b0v9`1?ReJAX4|{H) ziB0z9%O9X3!_ED=iKJE-7Ul|o9k7T=6|L~f2azHjE(#^HP{4JBY{BY0GdIu8Y z{TxEmP+>0fR%85eu7{|uPM!cX(}pBCsS3Slr-4F89#XG?>Do4JQ4Ir*ya)5Rhs(b3 zDma`fC>ZT2GD!}u5;~(mUDqwR>8(O8Ih$^G7!#|fQDLe#hU}=~KW5PUnVLV$11*`g zSo$_N=X^-~sDyFUE7tqmO^ugOe@INQ-T)2OM6)PnB}CceYt{bndqP&&b`-WDLnwwTP0IURsl-w)i#j@q|+^ zQyZhKMKz%WaM|-DCh)U3Vvyr_G=^jj#k~_ctY4q~38aQ+q+_0E@O6ij#z4qE?xH}e zi*WC!Vvb0wBkzB2Xv}_r0Qa(RoO&3SL@KZ=F zsQmWNuY>F&8{#*d1+KZjIDGor3%qWDv&=NQHCLT~WK@;>xbhm07NL)u5i^L6^pg}{ zaATTgJ;+|X$3fjv`MV#%g zt|NSF)h9AlTI>lRi4Q-RFGWMnr@NrG(I+ET4+S)_kw6a*WtK<&b>)cG4N5#wt~bnl zgY`SEup8k-kiy&JR~=%%>gwuIJ-+~PxBPKiv)==uJ7Lhw&z5vD(x&3@7QxfhpnOGc zPk6a3o`|GoBFmhQqer8H?V9mAi(61YOP8Du{1gi^m2K9kl|!EWq&BfWVxBCi&zL^NxBe1Fnr3iz}g$7j4RoTJwWDk&9erd1B^fZ2dyV`gMnzp}97+(G@y`Sh2 z;sXDKLnHr^x1N`Gk=Ov;=Tj{s^0|}}U|)b@Ggii=Ek=@Tv1mSBFv${+l+c?*q<&4I z3*$&0A`dc`r%Wbri$7J$K*pf76i8I#cQQ5w9T4x`_drDzSpw$H^DHvs;&RK(VWC@1LoY*Iw4u(yJ?p zioWXy4yiKumYhMVmP>IH{N^V>W>q+eT}b%%U|md$`^vk%`1t;<1&SdvtXR z^SLK772{Ix?e{bOU=$cMx^7WJgR+gEg;;4qRWQb!OdnW%Y!vicCYjK{>mQ+#Vh;&w z(`&tp<^mJjNHc`3$b8;hXUUZNK<2oM$YoPAJ8Xd2kI&bnh!jLXwFK!vJpLMT^Q{O9 z51@+m(`0(9CMS_Sm3?4D2RnYeMZM&J@FV=I7p|;6X9q;#lHc|}G9@dw%G^xxyV1q* zQ}k{)1|TWOW$m5Ckbgb0CF$Cqv|*vskAc$;TvjOLKBJ#g;^5`U)96+)@qbF9199U1!3PVgYA9 z_?Lor^AnN4{o{~9U;AhbdQHR@%)R(3&bUcUS$iJa?WaUQW{ZH-W9h8NPjDi|=CcxW zmAjfG>lem4w`}yd0~Mn{{~zRipt8ybt1mNBatid`uH$dB@Z-%jc8$Nmvi->)Z0wx_ zuMPU(?;j}X(30ZkHl`6A9F@1U_7=F!@Hhme0=p^X3*!_#gb{1=Ir#I%=<9@gWf+|;{i zwH8R@O8Xjed3e=v)l6hYXf9W9QhVaJr!iZ-+zcKPg3osyQ7X#{@N{Uo&M7}Ey)X$| zyL=)Nn^N6Kqnkc%xBw&6&Y~UB(kAt39H%SWxlE_$9d>NqKS>=6A;`++ zrJY9eU^}!vzT@?6kA*&aW?Gs7yp>F-F<_E*qNFaAm8z{%Lu=$>ClHS$A*gr1uu6C~ zx%fmTBG)L9kI*f+M4?t!Sc6p#o2(eb(OtPblL1u_;T*Are`Vq+$TopuG(HnK_Lk-K zgi?+1Foe9QR^i{p5DS!hSq&7F0dKvkfiEYKcK=B$q3m|Qz8i^MiDq|dk;P{vJ&$+>#tcIzU~FE@`LBjL)= z7e=4R1YURfw;mnUfy$MlyZxoY>F^pJaw3#i4>dDjE^d#HxsT?etAXL2(pTbmM!fi$ z$*+HGLadVGA9JZaAuVJb%3#sul)6i?t0~HXJaTV`7-hh19EJCXz5XXJ%lq;<68y~f zeWDCO<2eRs)mzQ~vG!F_S$$sbW3-)GzijN(jg7! z{m}nUFV0$ri^KK1_?~(8%CvUM3L^M3}*5d)EYhMk1@UFu$ZhG{8NS_;j;dX^$Xji5w)m+-L za!o&I2{*e{ut=2uRzG_%b`oe~%(fuGUt&%h&ioZ6c_*hO?u^-)KGf`9#@(2EK;y{| z_^5m51+~hRBeZgccrz)F!;tkeG$fR1XZ~_<41>vG;yVx~Bbr3S?UWNmA$6$SvuLde zDs4TBncF0~1jqV(deI2Ae=xN&Do6fcY3YWq6g^o+qqhEq^{c;2+G1;-EX9*1i(nq$ zX$i7(rG+t^T6{@hR3!O?c^s7tDfuFO;beSfVa)Uaz|2C_9-%0LTcN;qY+rx~L5g6&ctHK9h*O}e z)kyv2F*IeI1l84(Da;_((t%3N`o=k3@$#|XwKp>n1CkiA>l3%qBT{8arQn%Ixc zkW;r-zbcXRgr~c#oKAfwd2iJnAag*D_(ep2{~L&=-_94Ou6mD7_2Ii)Upp~IcGlEq zju~fcXQ$PKl49Csf+nC}-;PI{LCR`A?G<{JFqNyo^PQ3m&MOFJyou*Rq}Z3#K&dMP z{Dv5=(x%zE-_+a{ev4=Iw|6R(opaT*RXuItLcgxoK#(AIe{U|2#!#xQIC@p(K_|i4 z0M7m_(GzbvvH1AixM|o*){$Oe};d~3I4H=sS3gV9?Y*7HQW)RS!M}{gdC($H8{sU zN0M|_Ujk)tHCrKbm70EU+OmDhyU-eJt?-JX=~8C$p4g}Z82u}lHTwzTscDI?dWnoN|6uaga-&qb?^aL! zwG4*4cf|YSg9|lf;(Si$8xHC?3_PGDhh6SWNV*;2rFGiJMkOKdPc^78(gvH$-JDLD zn^$JToKM7bee?pTd4r^_L<=&Dv0-PP#^aEZsaaU`xJ=#^{@R`e!t`4?&4f>Zb&-bmzj>iB19V&!2;$p;x;@b0 z?itaH)H4SKFEUJb|JvL1ImfzV1iGOh;m>0qWO1c| z?v?h82N_27qp#<(kcA~wbVGp)IOAy#QH#(8p<7{psrNHR$NA`nPM@O`Q3WaOz&BLIa9A&Y0%7{C2GXKzA3t|-S#|iQ=Qw}IyLr1hNDxsvVb$>!Dw?yC z323NMNj$N*y2iH)Omeg5bUh@SG~uGTXoR947IBn0Cw%~A@1FvR?d7+@MK>Evk1pF$ z^&EFY_HLU2ig&I3P10e^b$T&Apw;*d30r?_kKB9r@Y@?t=yCou2&vu@asxeo>%pl^ zK)sert$0Pb&be+?>!My~+QtwZvG|{r>nw`2x$0zYs$yZ*ft21I7oQ1pub13VlE1=K znN=PN0cz?hB@0y(oTf{V;xh#WwOl1aNrQHTZ|q)+hzNSk3*bBv6Z%Nd^W}8>Q8z3c zB|AQ7ef?e7!H9m!=Y?DP#g|5>bD*I0H%+M4*K@+{DeZx;NgvpSe$07|M*R z{k56|!vT1t%KRUwE5u2~cHn5z=k5H@m$>RvGj(Xxz~id?F+W)#ra7VxoBY zvPgOj5)ERu>wXKhl?VJx%bFBHT49%uXi@#Kx>lgheo6lQuQ&d{}2Oaafu zwYhYJm)yQzOYAj}HUU+GWJ9mqFEL@V75XuSs9emHG}*&Tu`KhXbjLg^=D1J%0SRmO zxOVs>ThzG4vbjj~RNTZf4)8uBANtyt(QGl?%)){^`2!fOHWrM+L!>!b82YdE5j8ib z$#26^pDeL@==k>;-~820%ksoFE3~A5^(v;>HTO9lD836zm1KtienA1*!k}-FK<8iq zE+UfNNlQ<6)(MZK3NitZ_I1bA&SExGcef@_oYCD`caZ(m zO8pH`bQU>u!k4x%L5Qdr^$g&Vzou4q;34}TF~JFuJxB0(r&(wDrdBGd{`8+AajFRT zsDzB<{jll1G^gavgX6_ya|w0|l}O36a*K&oz_FT{35Uam{BH6lZbUm~kNbngZag{D z#>Mb8_n=IXnaa(&UjBLORyV+*=g5FgTG7Iu6|z#OE(NKTr3$C=?$EMSvh>jsBpX zo`C2_4Z%c;@;60|rLSzIb49-?6MGG3eNX$(-onO(+jjSY;56lY#?EIb=r|5JU2BtO z)XX+W7UtyvA&^MciZLYbj03m3XRCfMDg8+dWs_rB@t90T2dzDv8om`MXNHusxIjcO zFt9kb;~&9ldxt-9v%BRSl=VABJz||ZG(&=Y@v=OsHgu!1gXzCNn2sEpke$8v( zl4T$)b2QH@J|@QgDAO4?D%rCD1u(yJKch}sc|5cga3wQ*gTX{m_cnHtQw|6s%k!B% z6+;%9?HAN})JB1pp$PB!PQh$Xr#>8&BVjVV1@^le<`t~A^sb*5YUqW>CVA_u8hJPL z?N&^KjQw}s?gEed9YoL1B9P~J47HZHN{=?mN0mI(R^Dkpq=uYC$&B6UMQQhn(NY3DdmcC~a^ zMg;wvKN7|;uD@FtDX=tx!9O%NB=T3?iG92_ZiSlNrlwyX|RtYNwbUf zxrA2cg+JyF7+r->j#+r3P|eRy0M?&tAI{!1y|ka+fUgLe`f#LW8G3TD=h_j0i!cEX*XLT;NJKi?f@(;pR%oWD&d>kp#yR1<~{yl660egqxM^ zV9ET+Y*G&FH`@&cWLWeKu?OveKPE{GD@Je!#^lNqp zXJOsD*;;_E1fK%Qx<{H^K=aDX#tJo!D5D?;^9C;Ld|k@|FOgy{a00*opOyO_&bv6D zHOyPRIh8iTD{=iQFO}MWAD!6lUJ;T#3=OEVg$an0d131NiF)bdn*#1W+2xM-(0SCM z?1Y(QW6^19pkn(;m~b?jTG*Web8MEyW#a8JJMa^d_LFCwib$Gl;S>l=2LHwdXf0$I zZ^e99Mhd7u=GPqyk;9I}m$UKJ*RP~$`evU3p^EEmew37LUgCpb*_m;F?aZ0(bC2gW z3J@O?UmEO&>_$Bi62yI_IEaczb0KM-*EP06D@1il z)m7I`|s+Iv|KKGvIoYy&9Ka}cY z6)-CL#E@wMjDIv9Qyf=YjAi9(01m-I>x)0LPqxW|I2jU0skKf#d!C;#3mE+klTd9o zVKfHFNqxGUxlQnqy$w}cUiTjWo?syz0Xux}|AY$?l1!5d&PS0nx*>l7ZFpO zHqQV|4yAMKDnpud^3d_-JU8vK80v*oy)oxbR%;|U%IOo2BL!F#S6*D|&b8w6#_f`( zECi4d4iT%p>XJ9G=53|(kJU0BkRwUp2KAh{t zKnDPAb8mHyWQWhSew7!<4f2ZSg~u|$M4t zoCYT6miJ)Z&=Uq$r9dqgMgUym>6U9A$;36mAnPOre~^ZeR-zoTBUov8@|qI_31U9>v3~N=Tw0S=v~AdD zY`@PB42`*5#7Wl#Xa^QU$PEDlr+yn$1U-C|{6;>i%<(97?_1gI4$5`+f6RrzS?=A=bobF+f#3_D3y&)C9=WsR_clyLxpF2Nlyg~`gt|LwK(-DNjNgWU z0C~Q{VE2S>M#M=0-Dy?DXMBE7gj5bO+&7so?4b2!yF2F>lM2;_mf@yi z1`K+R72~I5KeE4&43aB}N8iv-Xn+-?ia>W)VvO1&nz2{uiDM1b8Y<=jt;BdgJ~~D{ zTk2X=0XLv~eRgm}xFK>kJ$Qr$GxyfasRJ|}=qW5wfFlmMhDN{;w)LD=J|OJheE$SI zyb}#Z^WccZ6L0$H=zbfp!^Bq_O8S)dL5k$E^^hmX@lq_4gapQdQs;akn)o}w9f#^? zGJ0M+Ry~MRodssN`%&XwB!t&9eoZ|?#;|&O6Bg%dhP6QFGnPQZ{{9`U_VP%=`5L*Z7 z;^`u#>$ws!B<*Dtb~W9%Shed5kSK^jKlDe!D9Z5ZLXiTlVeNU_RNtFj2cZWg*kOAX zR}{M!LL@B?z$kxGts?`=_w`Uh-|x}GCTV;bCp<0T6vJ-7e(mmrnSp94_C73ZD6wH` z=ssZTdWLY!AXpDsb7aO6NQ?E78ah;r>g)4U>d>+e0>V;Z_)e7eq~K<@@iW8xtmF0vok)g z==@ihvav@K#!{4-SVq>eB+J-9>iWKljDqGKg(BO=T2XK26eVB*!t`4?T`bQ@B{0E1 zOD7Sv@LS$qyhepwOdGd=U<{c_VI<_W`sQ<6p(9fdPeF5p9|DY+jXcEZ z?u5Luq!EB_=DYjXu2f)h(f-=2B82084R@X0(BKi8A^m zoDVc}cb=<6z32o=s6`bY*MBCA8gPIhL1dXd`us;@xJ;b6F~nF3>X>z(lyPUaye=lb z3@n~G#RM(i;}?p# zolq?G=)=#%h0z&jxOE%)eR8p5(aUk`2lpLFAtt|rx+@bsKw?8aUcvss z^o#JUejZZC207sMR-h}2ug=AD3N=XhJK0>OJA#6K$`x+g+9^kkT!bD5NyN3}Q(z({7G%CD#S3nK^5kifUzbnC;H;@dqD6NDSlYcmZfHm+=M|C-EWPk4~Lg6g}ZRsDF2<;I_ ziK7YBE&C@of(pTOr~S`2@rS>aa}A{4esIau zD71jjBJukvGLVbAT(O$)fzxk=mY`sd5gSlivul9HCt~q{ zEu4ThhDL^w4=wQQl3qtubkSs8@yq8_itp<|BQ2kTY4}V~Y&oe{GaxyM*9C*Cz7Qu- zbnZekv(;arSAna-ACwaLgDEC)qYmYq9#R_jhn4EP!XS=XtM1nuHH?~KB-?{jW57W{ z%VW(rG1*H8il&Ig>g*7}!qPCn$#NufRNvYI@|t2_nM&C(`?cHFT8;<%O*A@9Ov z`Q+vDyK&O_wq+XyBXaE_&?CoxFdc0^r&WCQ{W--}|A2J~r4g%pw|G`)5hb$Q$V$3v zMk>$>?M0`zemf7JT#Y8HZGQkM)HN_N?n|zG!Fei%vA5pyL`;_50YsFre%mixrr`M) zb1W?IAGRw?dy?K->g>UECS8IuP=7F06x%;T^vJ(v;;gx+?I0#tGt0q=Z3m;k%qW$v zvu_arcHz|xCz|}ETf~Qb@mnnd)OcO)@yI$xScu_vwV@wvV;+T zs%Ix7m;3z8h-f1;G8`#{z2~kHzY~F#cK6efHUZrbku}QWa}O_Eiud z-~PBysLL2#xaezHqV-wY%o>ktju0FQJ9Gi%ahVSqs33ds7$8+RcI=N?i>h6Trx?R} zW|NR9PnbvSS@4q8k;z2{B-wATsqjoGume+4c|Pj3^nnts0#K;w6A_597mh?jy@)jr zytb*ipNO?;lJj*D&ZSnhj$lcmw}R_-GN+XReIXp=`LJEk@voDp`*9Rsu93jf<5)3Wy^JN^^Hch|LnE z8v1a*%2d=c7yHgk3geClq6s4RF_Ze+SY8;zK^Zu{%DhI4@bHG$)L)tW=>QnZ75ig0 z0Ax=2Z^rUSUhYhgtZ|4=swfO7OIQT{6O4u6R}f^Ny=hEbNS1t8h1}D0eEt)oVs&jj zstH>&jfL#59*`A`bek98i?2A!fgv>_siiaOp~nYfrXzwgbP-Qi0yo3xf-EE3LBHI8 z8M?(tZH5pJSA7xx^Uy!t}&bGY5T$R#<@G=#K9-Y zttvNNss$uq3hz)&jyBW&>iereN0+xGlMVWZQQQ=)0Mc)g@L>A^AF~n zVKGk0Vn7WR1%`WUe^SU4vf!;ReS51ajs>x!^xZ$XIz+qKEXU-rtM=Iz;Wd1Oe`RMc z>BB_vd3D7zC1bV0m5v&`dwqQ zADiDN106WnM*f=`$}53w6ns6FdJf@E7UhEq2qth8h#}glw$lJh!wsaZy>p99`tGFF zg!9Cq6kjC2kK*0331C5DYGpT%gIH>GYfn7J(o#Dei=m4s9#sb?_dLRd>1>3{gNln| zv3fM%Ki1MMuk*>PRP_={J1Ni^+JA6Wj;lb{vtTtB0~MmnH~|T@A?hDY{seR?KQ+^6g>!CUpDXblb0}YBfABHH-mtIw>i!ZL2k6aJZ-VXmCH^Ly zqlXEKkh3cg7TUVB;Eqgija~iKgMU4MsYB!!4wv{H1|0RLs&=OXftUkw$~EGi!Sb;4 z@-{-pE)Y);C65(Kzs*I6pIw^3$sf1VP7LRvS7!4YMO=SiaoedTC}MfseejUw1&fY1 z_5x_}YRYq!BQ*)6HvTb}63ZcE%QAeP$YXr8j|Jc9@n(#Lao~FCr$3#{;HCT8{#6A1 zY`&JU@Y1W}LuUK*XSmCl#hAlQ&mF_Gzj#|M_*@47jqwegLy%5W9yyKsc3?4KZSv5_ zmNJ2bJmU~er#-FC_pO1Pvzq9vZ%Jkis}kOj`6e9_f~2~awc{})AJPri zDEU;OkLfv@So<=Xt0VslxoTmUUe5)jqX2}L!(T`a3B$)W7~-A7@+O?fMz!8kQyn6? zrG1Wko!xF$2ztf9^I=3eEk7@)yt|AiGTeS3pv60(n!~(piNTe+EtX7;13id@c-Z0Y z?B>=Fk*_zz=PkT98Wg-iWaUf8I_~siE@q_jk#jTm*M1+RAZp`mgQygxfWX!LQ~nH( zB9%RP`Cfcr1_;t0JqRn&&OGDhehb}nzDq2sl_ZGqF1B*iJ|totf9V;57jT)Ye61;s z%R-f;(8NV0X3OK(z(+>pXiGfkSG(9ZI55BlV0ybwx;?-EcJhYAV_FOR0B))XW$;S5 ze^1cSG3bpXNfQVYDRz|=vDiFMgl14XT~_+=Bt@;0E?Sx97Jr@&UUuB!22ESdp#O!0)7=70G=KWHcfkhL2lyaNB-Ot0@K{vh zZ5}z$@;W-WndjyE=*pX+76s00A(=yCf0vFZ1D~}UUV0}{Bbq7E_KeMLGy3Ks)6@5t z9kjf&#WZl#7yF8Fu1a_^Ji#NDz^ESgcFI&_ZQxmA+6mUpKrh29kn>ILG>=1FkGP8< ztH-iA2+URc>1`Un$+A-ac`Zw$t?Uj22^hfrcxC@+49BF~!fO_WD({I%p{md|KI6_r z0eAM=lJp>8k6+X^?pWdZx1Cu;Cs@g9>0wHv?Y8|j7mO>L%Yt!LD2SpURcf5C38{l= zTq!)GXJ>TmHoHc8>CZt|=>Kz4nXTzxgk>EyS7>`@5pKVJ%Z%*Ydzacv#>WOH9F8Uq z47Vtm=R&b`Sq-{ZXosL-<;;{sNs5pqCtvs^PP<+xQwm6!4`*)*)5aBdC;KH=i&l>b z>*&~w7hA^BeExeXv!1#yNJ0X^LA+6{dZ_wk_caGjV?SvX=IW4dWBiFoh{ew61(+fL zrr*JseYp?%0i41Z7ue)j8i6Gx=j+dv>avu@A`t|JM~UrKT7ys&v9BwD{93EHJfXK;Z{seTTr^bCdYc8{S03`{hu<{W`# z98lW!4^2OzTE{ZRN+$4M+5VDI@{n8esh;}z3)5gsk*c|+0TBi`!V0^*RrpkWC%`$C z7@^3R9W|&5Erz{x^sKVDZ$@eXxKVf#Ok6?NyvvDIY!&l21p)mkAqMTxdHv>lW``)9<;O7+dBIb0ukm zVz*XKJVJkIx@xNPalJX}b|+x0dA-%rd=^a9tE0^I8AhU#Tc{#J6@uv1H&_++s&{?F z%%U;)74Z|cbFT6Iy!lcLOsGkFqrJh8S%c_wnUxu8+_->1Lz8gzu!K=B9N`!mX z99^8jMa~mt0MZ|A!r1IqAZOKxI*QoG*=si6Lfma0BOP)1u(fZ5J-I~&LeqQbRTFN)y5#1a9JI^v2eE05v3qPf$btGus~JfFI&ds7#{ zsq?NpV6cwwJ4nH&Up-r1>13~0!pl|In}TKwVLf-B4`^%@gbig$j)Qg)_zx0Mj!S!z zf+vweI3BtM`-g(0#EcuWcV7n1Xy(yZNiU${$-T*~5x zW3^~Dk7?Bz*%Mh>ne-L{FCHoE(6lReKK#t6rFmc}mR(9Xar`0jY zHKwZ~FYD|N$j0~~!g81qmY6V7EH6Kj;nH>hEcxK*(9%RYN;yb`4}hJJMoPDYC64uU zYNCB~+;KwleIlff%U?!Q>NP&-&gF}8V8!fByY%-!?Rt#d=M*69FL8POlfx*d?hIA5 zGlO*xl5ZgH-bmtO!cS9Bn!TqHFY+7J%LNpir>H!$x#hTmXIATyT?APr4HKGJqNJd_ zU!Fj+6^-T+*bgYbjoz1xHD>7d#3;X^BxzI0zH8q|M=W$~ykED3Nc*reN=()-#eLknP7uDVoA} zvS>#%1;XT3wd&juMA{l-V@L_+30qMKQwpQd?4pDowvEPwe6J>bL;F13zCDp9pgl|C z`O5vvpBx#Khd&S5L?x+ri4v?#L)>feCB;Z`-U5*RxTgC__ohbkqq*0D@;4QS=|oEi z@jR$=7=}f9C8KLKJPZ&-Z1L3ONZBiR9`FY&bnnT9aQq9$Wb9=`Z#29Y`U8{*pNJ_! z)y_EaSdCm0?<-a`b@;r&#R&}ZH5%rz;u28%=$X&@7gJw525yfg73naHOT}D#yT9P9 zRpS*^Rk(;^s|{kpHjwIJi@i9Yij&KK|FsWK5l(~qmvO)P`Rvohok2&|ixrC~{1n?wM&}^?0SuB=y#lW*c>hx;L>F1(E$%lGqvsm&Z8zY| z&l)rhf3WB!$T;2JX*zNMvrQnz#7>iFT2E;@*Y@I)%a(sK8l$_8YONtNeGd(Gkq153 z_LR-nj+!{GXJQWOU|F)7fXG&?**K?^RiG#ha!jM26}+~R|1V8(twOCy?KCn@v|$&9 zvUss90XMLDK^Y(Ai*q#Q^G`Q`ik$k~gjDuiQdI3^B>70gx$a~xQSODa2n!h{y8)}m zn!5iVnEv`pD5zpS4Wt;iOOCg;pHO}G)Z24N)5(zD3b$y6>SAk219w0Y!D`=^4jpcx zy1ykHOMhq3yqV>a4D67=o?J6zH1_=eM3dluD+eum%}~)7>tge@yV^OT)Mn`3Xu6}g zN9CPN4|zZn7_h4dwC@n4(Tk=XW*D2!46O5LX)WdD^2NDRkm1x>xBR4kA|y%-_c=oL zAow8JFsgGfdz)-cm-WRk@_XJ`ybKIYh&oWz_(#DTl`}}_uO>vUWJmr0wOU^y?;r{3V^Zz_f3!fp9@ZWOcQS?f@K})SBHU%8##lou3|$5f3`oqfakfqdPpspsVST32j-I@eW}5F_f(tHr>cb1kcpnS%$Ap8xHgi!d$T#S zf6jEPP}ak#RbVm)s?humNz*@-Qn;>xsbZGuv+|4VST3YFxNVp2G$yz##=h_!A7EEO zrP5na!x!Jdmji=YW{mB)noYq<<6Y4oad5E9)CC%No@8?n45h%20)L=%(D?zXr<@O) z0G)PpS^G0Ku;tvn8X#bYe=KHw{Nfg+Np3)o5`lu@Oa;s0eY?a}&iUVv9#%~EWga{W zlSu?mrA{W8?v=selq}oK)cXSo5=~V6%*RXf-vXLYpHY_eg&$!m;cr*&8Gb7l+8Oja zQ_RrFc9jVOvQ>g`%TL&{ASd>V7+Vnsl_x}9VNhqE<~!3LJ@$$AB!A) zSiJf;VqbAu8!51zk&Zpb<1wwB1hrK^XHc<#T5p~L>61O5848OncyWbnmks3BGeKtE z8q`h(AE|vsANWGsl)sRyvo}9&#i5jn96`%FxWs&emyPr%A@qAk6ggbYxL@1?YEA@8}Ne43h5IORLo##(t$9Y(H;}C=87?L;=;CJ%uJCI(T)iu>=DhBM&~B*nFZv zk!k^`$`!aDc{9H{q}aRu!Bj!#ChsQ9vEo)Rv2xyDti90Y=4+v__mW)LEs^Pa1SJF! z$0rp_D)m8^13dz<;%8Ow;srWiR}n&$ZbOX-?&u+c3U!}?Ni;?-Hd0%FPwCX?tT@;s z-6@^S`$qWTnb33k+IWVw9?;VLaday^{4*A>B%03HOv$RTn|C@}S>3d)jqMb`GAF8U zJArx?Bb6M>uH)US&&@w@+c2Hqw=wnWSLwe_R(iYJ@RRqj<%yUy=~pXS-ep8mMbz1- z6)&f{>}DC;CCiX&Xk`iLMA-Z@XwZi2;Gso8(YM2(ycX)NeN;G4%%(n;#*n>qEPq0U z_aop{AOj%2$qp7)Mc$@|>_P`2Dnkwp==+8>>2x^m&p$9c5fTK_@!!@lXD8JV#5pb4 zDo63pJ3&@-inFN&fyE3NqEH~VKz^s^JDJhjC8GWjJ|>Hh?Y8!;_k9C@y$W~+F=A+K zs9UA{EU^|uf%Yue`;t_;N_3Uf!;xb$n2(YmMfh#PFGfBwRIPkqwgF}_p~Sx7^#Fet zotXGuS=ZHoB+%>*FyYt2(LGr&Z#UJWAhxVh@|qWA@hHh|BaZYB&vrL^XU72n%cnK{ z>v$75x!`{)muJ~|R+tjZm|<)TjFWnIIGNX0gS-j^$@U}qNBF1Xik&*}tc)0#d zlQQ8X#g#ywJwWI`0nzG~XB8E!UyZ-^iu9zEELm*vUie4AE6AS>&n$tK=TX31x)l|dmY;7El7Cs8n?=ycGCG_E77KXj z|96q_{}Ax_!bBZkZ-nx6cS`riC-j4=x*$O>)7>Bv9ZjB;I6T4v|imIOa zKsOMwJWar}Gat~;J_`7Mhu@~W{NESLE}tM1f^C&oY5Fwt%y5-MPEifB2s;M;4EmUOzz_ZL3kh4%eu0bAR?bRJQ8_zc3h)}m z1yPJxK@141@mpRow?OxlfW%a_s$H*9p##c?v}k8Ad*6H4L#8(rs=GMto;N^yJ;dgX zu|#-IuA@`*DvvU4O|(8I<=m)=ItM0yWeuxC&<03H2^!-ow{z_Xh7QO$r(WA>xxDm% zE~IVkNjIdfizvJSLHc7haD0VzOVIbwUydwCkGXWNEt=WqG4W z@0DE5r5CVy3GQIxAM>mvMZrVa^%W#?is^;5#;X)@Yny@eEwxY?0o?PRBNCK0--B|Y}fjWfx#S|OPyfL4NO@!$~)bI zVG^F}HS}fTki%errSa@@4o^WurF>4c3VTzk^$!J=W_ssk)@lMMP8<&R6d;xqzYNu& zmD)g+OE8D3sm;CQy5_A33x**Rxh(LZ(^cd}{f{MUt86-+vk>OccO?Rhi;UUmKXA~^ znk+p%p?&Q7njV0(5wC+C7}weDtS+U5BKkv_J0_mT zuwY=Y!Qnx$K0@llGa{gu7UKNn8y7248Ed_SRKsA9kd+*i|&P94r*w>6N&5?1Sh5Mu5VQ(PWrQeRa??j$Z+1UyY z3O^@tIxBfKpmF>;?~>qt4k&9*+m`EE%)8+|kFJc)xgPKv_BDtv&wFl+H=T+x83nu% z{UrS!)_;>)QJ|)?tpBnWSI$yN<4Y2;;w4Pbvy2!Lo!6~DK?Ti!FfH)^f^g3=T9ZcZ ztgDzqvM*S|4{{${#7A~9{2Fs^4NXZt{l1UO_~rk4gm+X#R3qNI(ChnnIRFF-;ijq>3SHTT(}O<5{di64UDkWyYIP(nzY^Xy)`aw^|| z>;*#daj}2nr#8vBprG$&lmBx40;`$qB&!KZ55O<#{U{+&j`08;oY+?6w!Ta10u|A9 z$f<_1nEPKj1}ykmT>~<|*QOXkLw6<@RsnQ77LoV&mpdhRndoCKeUBdMk9>^{lSWDj z6&I%^P|n>>=>k+PRj=z$N69997Lpl}8uS6Jf=D4^o1PY{={-Bl3tWF!ojPqPou_yl zwJX%MOgj(A2O0Bx)!OkRjc{KAtI&2GLGo6D_74cf)wwd}XQ*l4tJc%3J^`c^Pk>&2J1 zwf!_-6rEU0WCAabmzLpL_jG*!&JDf)wL^YFeCOK4Lgj5!d)@pw!O)oT6ERh!ctR4~ zt8c6N=TVBwR3HzH)~SAg)KgX)O-8{_<^vu0|A*&E^4 zXIdv=%*5$$#l~vDk>3nmzy*gCyVKbl{PSEBJL6_LkK|bmxMA{7@_l2Zy(eO-KJv&j zak)GBh{b89g|i0fIIu5Ma(Z4xuJ42!1c$BmcT3k-trE-RTx;(qC9z|*K>sdX%6UY! z%+g##vk5Q0P459gqyVQo)F7d8W7DifLu|kMG3w1+=Luh0v|}u1hm&RdQ)#NhcCQN= z6bZ*iIbb6c)LC)2M>U74Qx51m&6sre`KJC4O>HVXhk2CJr{3Yz9`FR1YASUr3m0|; zV?#Zblsjw*Ky?omw9;$3G2P+meWk*aV~$L(hAPt6M%5TRSr?jLgB7!$NE7G0D#xl* z|9KvYPVCBnn;FLpMNjaneAuHs9_*3k4$pru6<;wnUyX+>_PD3zoU|YFdLlLHDV)=` z2JbgdP{k(#6L#N)5WA7#S~}dhW70hP#kFzV;;^(Ic}jvaka54S6GA7xv&%$1!oPjX?-d|WfQlmX+RgWMGKBX`7XeEi9Qhf%~ zN1A@)8Q{AvRss|a>NA>d5a^g_TwMr=3cU`9+X>ZC%ygMjkmTvsTLn7<;^`+@9fU*~ z=vRMxm6e$o&p92QYx`*YCaUdta5qWmIp89`=k?dRy; zE`9r#G^xgMNk!9s%W@IRwb zwBUgcueYQ6!=a&8K9#m&tB9`N0Dx(ffBe~+?*-dg?!>DsnRS&Bn=4UANZ23h2|u#a z%lm0Sm?Bb13BIlC8iD)bqA(!+gb&knWvJQ1E|W+w)GDvGh5v`A8k_3s7F%+@4X&h* z;pQR-_TAn7R7ppc2}slU-EwNfK-*jB56qk?p8NnNbo9?@ja8bbCch*Hpd9b7Lki)V z{JWmGc0tn-h?@7Um#k~~nF!2Qh@=r8c0QH0Q%+PW3^d0vJWf&hvmvA@tWx7M*e@Zg zvk~IdU=PC0)xwsewdm!91(=X3PddbSnE(msZv;u$PtN^U-G{N@q`^9Oevhg$F`h; z64w46%#C=bpM=WRkZ@q;Mw;N90|#U%zg7ITcQ}oz1rhg>gE(KyG#Xv7tkGyQx|qhrDfe#pbj4e*)x|X_)Vd&DqoTQNV zoh?iSGvUBqtNV|;bp#~r<=D?b>#=tO`{mURpn*Jn_Ifj6Lq8@aWjV}=s(p*N`TEnD#yk`D$syn2?;77x5OP&&7Q}xwoSME!`hXyGPYQWs;^pD>pbdJwiVmnnB>*xEh4s z<}H4vLG{lB%A+|TPSQhCdb}Qv=;u#lg-taX1C69fm)LJ1+q; z9yE`FD)|X%A7V>s@fG5wR+9Zsnwn30xgbm^zPr8_XRI)3K3vfsOOd4ED4Mn#6Z4*3 z?fobMZ$Y62g|xrb$sT7mkKL4F#C0l%WAux$-RnZJ)awT1Yvpb6EbRA*3lK#7ow*0m zTp}X-%~6e!o3oJf9nO+2dhwm6v5dvtw;0$=W_87Q|D3iudT z3EiIo{cfAKj}>g%dU!Rl8_FyxRj?rz7?sY@+_`6L^z_L2)Py-}5m9Q@H#TN@50wPT`}7FK0nT zq4|ckJ2uw^+BV&EE)r%+)F$F@;~U>i91n0ev?xN~HL1sW`!w~N1hRryf}^V`wxIXI zQHh-6QcpQgZ8&e0Mi2cYo~WWM?v##A|Br@m89b~-x!cVA-aPUDGkGA-GJy1gf_mf%&DSu0F zNH;i~8#%_>sG?g{!}viz!sG#5Q5bR_;OV2fLyO2-swgzi>5EQh9eap{OG(rK>>-z7 ziJUqtVs{Xxs<2?p@lHNej%kYrwIla(1b z9U0RgnSd-yFIVp4Xf{*8y;D}^;CC)Bxd zrB^T??_YZ+m1xHRH1#E=t^!RP`wHmaRq+?UR_zQ}F^f$4=`~I_FxOL~PvWfPE(y_|S0FZ>>UMW@X z^}`g|4nXuwRZA`n8)dL^mF5+`}M_+ko#u$vsI4yFs*}+wL=hD+$=x| z1|3{Qj4ICio3@rKv76B@I7YANeLy61ar1uxV0wyg>MH0zA}z!_;L1ITOpGR=u>N-G z(yD~9xA-gENdZ^vvH9w^Z+b5xPHGcW%@QuGbsjB&fbdCBBwb!F-~Hmbvmzq^}n#Mqp~`bR4Bsk7GrmqYX2%2e`7Hj(-rSbK@xr&ZdylAzu(^YPqkR6c?)D zqjpKgRK3U(UE~%rK_n0N&zu!Q!QS$E-)w`vNUN*2b3D8}wV3N}`$TIoFAbC1Jmi6N zEfYpe#!b2F8^1{jm9=J8KJ!AmkGu01CN~}{K8tt;_W&fYS_JU8-iacy98Js(pV9)0 zbLLqO#7Ii%@DDAW9#99Mm7|-SV>oDc3GarxWEI(J&mUPv_9GM23$ius-zVb1mifmw zMOWpz3{HMw2?`5$d+0?Hw+_%U3@G{Z?wW5@a`jCPFvH;83jc*`@;-V1Tf7$RF)V&j zsnQNCq%Le(5j}5UP9jh(_#~V2A}qw5TkNH_OJ{z&5+jUg&f6rm!7rXvi$$6l(NrVz zSHa!fb99ecg`4q5*%=V&SNDFD@7!@9g)`P2C3SdF56c1p0mp%sl|F9-xuPCE+Z+~x zY*1>^*bk$-qu0_rQr-)o_;TxNxXeXg-RyH5Pdbo)0MYbYnUt(&w$C@G#ldH8ObYZE zPp@BKtVA@|*djKA8(9nfI0q{BG0GUBjc(O<2?}~&Oyjw@R${96#3&$fR@8#GFo@6s zkcMD3i)r)h$Lky+!0&=}>5yU@{qo!d-CMuaFv@J2Jr-oaG!@Auz-x97pBBj`(Mn6a zF`9{!e|K)WS&q0lIoKf!%B1}9mg~&D1(0b|%r4v>>o>T<5vfVUi|Ykn^f7(mzNa@s z8UpbE15~ArE^%01zsNh4&R_Rh2urb?+QVIYjLU>}*Zw6>RPc_RFQt?NI(h^xbA@`G zVxj6W(a+)(AXk+8p+D1(KFIy0iN#wj;Qui8mT^@-U$ihBLb{O>>F(|ZLAtv`x|Ig$ z4(U#jknZm8lJ1ah5CjD77yaG8K5w4O>vK4)&zd!R_UxK(=S0Fnmbkf}MndGQi+6W& zk#7bV%TF5b=<)$b0=q{0&I3WE6Y-MQD-By-RKwaf>(Y)_fX{k?w07%aL$^=f1-eog!KKCovUoy$>vp7iz63MWTo#S1SKxy)ug# zur`op*iJamy7Uhurh|wHs#6n}N<0~qyYboAmm*Rs$*f9=yy8Vh1;OQrz;kgZ>Ff41 zUarO!>&)brt>6mpYy@lH>HnO#B&%@y)|4|LxmYN96#GnsyK& zl!CjA-u2PSZQH7$4351?oeds^WbuYqE`G4ONE<8Q@IK30QX64@;t>{5R*1?kX51nR zaJHW;iAJNCT2~6jLo>3}cnZ`0*>e=wNWXqLKnH{cWT3FNH9GZvNQ^o-ctMBp+8s>k zh6Csd^=wROozpwcI*K`nr{Megvk(@7wvg6Maose0_U$~!Q>`cm0>m)ALo#FFQO0g5 z?y31*xnduRn3_@R4r0B$aN5@AtrX) zYCh%d{y>sRB^;)P^8&Sa1R#IoSOSZ;z%^+}vtD7EMT%reV*vu}FF%a>EJ|>9>V;d= z_UX(Uc=WQ{1d12EmQ?EvRv-Tb^z%X-aFLd1d!6l7!Hlxhzg2LDpk-D_wSb{;-g7RE zdc9Bb1W8*DMsFUmti%|snvyEWBu{DD4NF0(_FJljn167ly(WO@|E}CgO-~GkH_Hea zoi3sD+myZKo*TzuDGl?*Voisdu}X0ep_Qvi*O%H7rjj|D*Mx-kCOYtyb9MICFgZglY|n$?4ZQ`! zA}}nM_^ipvmGJX}RO{v5G2JJRJ?(!Em56-zAh3^S(UG!{~dkZH6bQ5x?dKxrSXP>j_4i4?S)n$jf z@RItl9%B+Y;jQZi=r+yoq1D0}VHHJn|KVvh`Rjq!?W{4XcoGeVQtoasyOn@EJk}Pv z>BC`uzYq$DP&PJeX0dA+eQi9Xv?f5tr2L^#rk_u2o%r$_eMUe|6M*SCyDlmDehAhI zR^t{bPWIDsdk!ip~u^|LE}!^ezkFMl`ma?tdfI8 z0)&?cj}lM=SOSjM1%|i+2efR++zQoi+vwbdkp^#V%kh|l$k*7$xbvTR_*%z|yxH}c zAJ8xqOG{nbq`}3R%fUly-D+pCwc}qxG5c#TIXK5I8b}EDqO2*>9IRwLh7JAIq!Qat z?5fRD=!`OeQaS_d03_Q(8`8Z@C?#dA)h4Qzj~dw12XL-5o3qya%Z<;ZDf=`5%dg}+ z$W;t07t%l*a@-E0@^p%L{2E$_sFq;b;-95EepT6mmZ;~XCraomZd`$79Lh?b!{1fO z$P`h0M&@G<)V;){tF#zgm~46^h$fh?f6$I)>&e-JsO8e;J zhJiE;E&rz4&_8KTbmD$=yPm+At@c!NpB>13)HPwZ&P=RGBc9rF2N60m>Pfz6xMDvg zjLuousf#2)l*#a-BRx;5lHPe`6%P0)Aaf!6t%o5ST@9s*^OT zQBr&d?oSvYN}%!e7h#7lKyzNl$!i=sax})~P`3nT!bMj<{D^_2^M)F=zpLQW@OuO4 zuF5-Nh!;I`r#2lv2BK$2NaHifdBRjkjHl9iz`27QI4SC5af`m6N3Q04QijF4H$n1v zp=I}1;qtFK)G{D+vGUj9n@Rmv>bs>ebpmrW$t`NO#7%+Nvd5g zn15=`T;2K_yW0k_Gpb&1H=~2-B;+f`Ba*t^dN0P)zD~_5hTh#o8 z#ti(>LmkhLG<~<0kqeC9!*Bm{oF0E*3T?;ow10OmOz|_WdMw|4c|NSC^HIg}L%8!e zI1t;xt5A~qS}I;mTxf4}c-w2%RHsAGz#Xjx=lrtsI;ukkz%-+Mw;Y*9zlOp&g}F4o zQfMw@!jaXh9iDU|c1mCd!=<=(aT z|A(j58#F8p2Uw)8dT!pddFk2#k`2E5@I140_5dF4rg5Ow>|;BHNGen-@9YA^j$O3U zE2FPd#tTO*g|V0LLnjQ!KtvUSUphcn^E)E%aqtm&q6X7DXl-XQGR3{$ zv7L1Yi{Rp&L4){c1A2Qx#c?|PdlvGBJAt29lm(8~Hv#|LRtCD0t_SABYEr~aGLPz@ zG>*&j3+o=qVm~3{%@Jcr@&j$y_|t}6(>$Z|-NLjw=U_`_iEt*VN$+F5>3q{_nxXWZgvAr!Y!Yx=m(j#T+}) z(jW891vE~2Y3`89b|2J2o_PA>%^B}qlln1Ds)NC?hhK%4Ul+jKuj3Dzopv4h(J48` z0KKk6hwU8-V*?0n%GkkCS^incW_hF-E$OQ77BU)Q7I?A&nru$rENCa#za8MsxX%%p z`H-lClQMtJ3ob=m$@*4EwC8E*Ht7iC0~s-2OY}D~?cg*(=Q7x~8xz(v(_=u)Nc}tt zNBJKl0+g8ep|!8Pk@BvQ^LB!W?J|Ba0;wej5A&_^1{H>YGbcb~06_!qOO>DHTx<|5 z#YF755m%pkma165C9zHoOxz>_Rjp382lNrk&g=5Xc1zgK6RydnZD~{1G^86CfG$=c zpCEmld#F2Bs+o#?>k@n)7P<3=w6S%yH}=aBmytyiGTrZ2lm1h9Dy%oO?4xXutTR{( zBcg!h4=C2NRlzz7X{9+Y%B-YPpf)#eM86xyVW$48-z_G}$$En3r8B*DYXA8dJHvb5 z?@#y4=lJ33*J4ZsWPy96(e4w_?yqdSxCHeAJ;mrWcDsWwqu&;}-qBGzA5g*G|GI~J|mNokz{Mm#sEQfXC+Dbtzl~1X2{fD<)0!A+KC|^6lyZ!_Irnk9_k|(asom zqd!eFO#zUe z&~Ny@N1w1RgMOqzCw506d{mmoMUQO#2a=k2H~u5Cb{4wMqKM{BioP{sc-7gl3|Xdq}`sK}m1lSES2v|9uEnPOs0yi!bwDjnFs zG^vvv7^Y!ZV9ad7p@@vn_~299Lmu>o1;R&TM%r&rig$EijdUgs8JvMvsRM`W{yRNYA+#h-8M>tb7>{ zkNwcWMm?z@VmuZLW@dK%`Feh13h@Xm>mOIHE;eO2qpmA0gJv@uN|@4K{nxn1EvwaZ zd2SF%V(3@`(0j)ONj%-@+9yqFs(17?X)BiK)FHIH)T@87ZIY%rIv;}%!1-p1lY9*Yq zX6y=hvQtH2vdut*Ms3KxRPfk$j|40!%-;=B=X(XtjqixcV{Yp$6T{Zg0Ggf?yzCLo zfzlfT%RE`-`n94y5%8JtvprwW+g_q9IKGy6rstR@`uCCt36Ga`rd*o@Z@w$8(f^o3 zlP0@zSTPMV>uDOS{D-G64xf|ar6sqpnC-0%l(yy@3H_>+niSr8?`R;whVa^f2x0rS zToOU|-|e_=epn9)B3=b_CxvS+z~PEq7#_jeJoi$!a-BJp%#=3M?^3MDcV0XOCNZ@? zoJz=B-#Mp3w;ECRkHim((e?t@tsaJK8CN%5rxEHJP%}N>^`_s8OE#r#HfRGgLb!8# zOBwpZbl!HBfxu_h14$B_^Y5B-*d%J4Nh?u;0G^(cGO+9p2ruh=AGCGX4bct{dqDW1 zXJNteG<*__x69zvtKqN21-p~Vle+Ql#UkTr=8Q25yr;O4Rbyz`GGag3Sb`#fE)+gj zYgCbdp%cklj17rQuIe6bcuItb=vOlh-m4=&s$~I4&#?pUCdA3i&8pFwU^W zu*Djh?d6>xRpjas$OSe1rHP@Jq#n)(au+VAbr08s&SnW)Gda*FbLC8F>qiKy?-DRd zMFah2r&j@Gwmea_jc8gm`lW&#lwEhMN);T~adyj-!r97ftvqPu!JD<`^?sHr>7CUO zlYYaVhI%19GU~&5F44Tn~8X`thx6 zVIJ8;g4v#ZCBwD@FwP9L7K6S!#u>@tD*b|UwSKPbdWK#;7Lln|UWcR}H@D>A3DVbw zmtjyQo?QO$7Ie$>-o47LaP zUb>*D0u-q;1277qS~Ir^s`y|{h+5Pb_9a9%!9{J3LgT$1@WtjiXx)XHGrk( z#KH^A`^HV(sxNhUihdG#9ue8zWUZQ0aBUDilxa8|1oZu7iP+>eZ4HK@Q%D!CpJ%iF zNAp{#ua4JpL8O_~G34iDR3Jidy0m$bAd$0!j5|*#gk!sLwMyd0TWkYW%8d3m1y|Al zq~~~Y%9Y*^N$I%zkV-n=<9s3YR+2p*A4m!m6g454yNUgNKa298ce%-Ms^N;Jc;G7J zw?j%(7vuNu(U1;}Ci3(BJD?&(#;Vm$`{sUCqZDmPDgNj+k5V}M`vEeJE>teJ z6+iQ*@a1zb$;GW>3|0-9v&GeH!dZg%BqmhH+(4+F9ph;j zqd1;@mCJ2ws=>tq1LhVIuBuJCy1~C&PP7tZYE3XWBQ_p)A8jyANLL|}!sw$OqkO@P z^<^-FEzqi?8vp3(V^3;w2gwvM+0i^PO6F)=E53sbfrE|q(MqkLc zqV4yy1S{J})K#}2!X5Oe{ATB5GvoK8vrPOW8_lPC99GSTAEd%@7?0>mL6U=+AyT;<;N{wDX+K1_?_|i*f$S@JO_Bly^jM`!QoU5?Xm+jy zYo@kM9LJ*^!Xy>nDG1`vtIvu@k9(%qbODd@R%d#Q!bOxOAe+8g5hiVVYgh;U^G9kp zEX_)U$-gY=P?$84Nk=v8opJ=8m6?Am3d$zLoIrHHK#SdfRZ6W3G?i~|lu=r!N>-aS zvW~f~;M+I0`L<6nYvDsoa#^0f*9IUxr=-FAVP^hrK*Z?OvNz-lmC`hp3V{nDy7kf^ zbt)wE`K!slT7rlgVs4o)ZIy8M{iK7)IqkPMbA`hvB|1zVN}+3J2(;Y7vj2)XVHBZm zGT5~y^m0xOH{fAz&wJ(P!K6vrDKP^C!k-0+Fc~4&PZlFVpu(IPO5Ds&JF!TL>-`q} z>Zo!clg82XUr5OKg>|~PK_C>fRVUjHzfC&E#8O3RpDN+9IC`~*$~@qyfaV6D69{Ax zZZNqjzpgis#hk+6@(a^_hgFD=qd6o$O3nZ@uS= z!QnS*Qp0agJC2|p;Y_QHR1(hi(}^igF3doPIQ~L*Lz-|-3E8an&D>u|Txw1O>dwaU z$5B5rJKvRt%YQ@%>%J#Jl|!vrvcYK905ZPk9~XF{bq*8R5~s~p$4Eb=G(px++^qX z6teX5WQfM|WV~~aU5-wjgG4uIKSS(MjeW!}8-ZNCl%Um`TFy;BOk{l$xym-7FE`?- zo5E^9gTN2%1qwPqOwTFav*+jW1M4IRu%)wi2K#<*r*7}I6flkp`x@cx87Jh!zmS?= zW1a~rAG7nxwvF(dE2iS+omm`>Gitu%zNO3VD+fBNUfx|OU%=@(&3S#Uac$SHfIfxS zcKG4c9_V42AWhy0u=Jc0C9FRJ4`Zz|HNt|f@^r$$7PFVYr8>NKpXvhi)v?@T`$E;YPc255RtDX5fDvHuBdhF`OLfI-B&hb9Bo+<3N0{Z-!Pbw-sp z*t<_F_aCMu?f8u`F^v#9&^InD&TLVZY)CJnr8U8+Sf;Nc=x;-S=zv`$pGCo?id#A{ zb}6^^2Pm?`uhhLuo4RZQx_p>lfYM^1kA#n#(L1kzBw8qw%Z$xgS;3!Qgm{Z%?r;(Z zUsYS|Y13&uz~Z}Z!SC0p-T2t?nKKxpw;qK4|<}n zx$0k&el0Wj0-ixNkL-Jp+PgPI00Rt{fCz76eYLyhwAG*0_yyaL@hHwbPJ^YXAs+*O zSJ&_HBPV5WMrvI zZEQQGGUlbtBkD@S8hPu98LsCjVP3`)E$o}n}N(F=bqSuAhl z(>S=T3oD5yIX&xCxq=XfblLuP039~$AF`pgSf@3=@Y0FvV?>i3u|DKE%wIYAYHfay z292EJLBiG!-j+LyVaJVDPnq&ULydczF)dzz2tRy}R=ttI(jnvlOV@`YnkcRm8Ml@% z_Bg0WB_9SRKL9j6$7@297LUrpL+)98%{rJmF2@AMdz6)9CP0o(SaTKCUHQ+@bE9L< zD(wnqWWpP%9!bvR9Y#mlW`|Hz7`A%}AN86Nbztauc#wdagqfn7pGA1phnPvtAGDyd zB_pV!YnmJmZ^thHP0uk-4$WY*8*XSDhA48l@;Kf$g=KJc{+}hA%v@QPR*%O2f#le@ zvYiQ&agM0hs@HCMb|WAmVTifDv81_oZ&v&cl?LcBh>BR^CqU%oK$0k0I&nLG(2Fcr zs(v(_NDqK6oDy0ESX!8^-$70J3U{?Qujk_{%k$Vtfyu1aJC8n-@5vY>_j^KW&Jo?r z!O`*n+D^d>;2oI*DstwPD~!I!VDu3U>Ta#;zhaV7ibv2(GdZ~Fh^5ItkmPFlu0UV9jxKK!gnN-j$5+V#1a^40V!8BPR-V$+6?V9MztiqI2c+ zW>^DS+9Y!)M=;yVF|(3cQUq>nWKX-CT>EgKv*dcr#(d6=4*#=MDBULw6_qMfP(q;xZ+-X?%lD4bC@T z5)FmrT|y1L7MWI~nDlTUHI837KmKDavkWqJ-bS)r%W3NNwP{0-YTOvI>y#FMHs+(q zQ2#y*436Y_6S{tJe){#Z%7R=cTV!*`XW@X=3_^B$1doTHZbVjK>7J9T2U$lyQSqlo zXeKquL|WwQ>>(-`$NnR&Q3yG8QgHEw&FkO8qMwEeFy~iU>e6abY#+Tucz^schqweC zn11{8_fO!lU%f-1(tDFdfTJ8>q#~Gg17bZ0?IN~gJi7lj%%+R;yU*{B5cuW>s8Oe1 zakwC7EN8z+q^DD&JILdQd+n{&+MC8@0mK}&bxZa+Mz49%o2NIsMq;<_FRmMKS8Z9e zI-!CWs1AV^3XqpULuG*IOy%X^WCdc|qSyK%LIq6ZyZ(K znyP4e*Ttq2vZ8;W&u_K_t2%%T<3KRvVy*UH>qF$RtUdv*B4O>hr;F2jJH_|%@U+@fz(1f|z1qsPo# zZiI$F8OgI&u4(Gma8yED;@bqw%R9EyZ|R;xCDW-M%3Sn0sJ|Lz{^@JyqV$+>e0%j~ z6a2fN-zv&ZTTwZ2hl*qAkbz8iDt34k&?2Ps%{+XCUp#u)&Hr&(kEG=#l>+|Y$nKop zIeLku+LI6VA#g$aHFqYs|C~~)XRWL4ZLp}z57%ElCCJSlTZ{zAPc%`)n){l5s0}Ta zufkEGOdMEEnfgxQbMUHt^aWyx^FG_(mD6UavtKXQ^lL4gL-)lR!|``1oXOjG<$V?r zTpEfWEeO0Y3p0vNf6~AiU-NJ&Dy~sdvoKesxQ<70f`x?aMACK!h$)>$AS!{o>=Lq- ze3gU8L>Mx9E@ADB#ySo5($*8Cq|q@e+MnBu(dqpT3gzYDJHz<54N}5g_i*CS z8)zH?PmumIbPuESvtr7|5+Wwv@y-WVf)B5Ll9$PW5$K}r-Y_)3YmdwaZg8{iRpaBx;%Qwf2R)1FMKK~=#Ec;2qL85B{w4X_k@L^$w1_Y50YxynAf+4`?-k;`x9C}jKU(@-cK62&h^fs1&pA;G zf9#htM$HVy6Y*qJ2-H`@sZ3WKQK^3kIP@G>N(QHW1=G2)#j)gKc(+Mk@_VqETiYNe zs7kG>lz?mqh=MRPwFUdGYlNr=D{L1d%Q}Czn~R&#Ftdi$CDAPV{9=5b&lEUxYX~g#XY%@|WLpnim``p)cm_nklzyAC)=Q%=-0(W`k);$sAJwyH>WjD|F4Y@9 z8!17LKJOrzw2>x7ffGVG2Pht%{gEeUU`)&kc_9mhj!-l##ujWA=#S_@}|fTY$cCM$gRN-SWpf z?X(8#k|Nh%D#bS0#l4%PI11_iRhE7Dv~pH{wX~6;5KYPVBrISt%G_6wM61}YnCSMs zlebJ*tTq3v-1U$sls)JD&d{{2bL^wlN`eMGv6qkQ+7N|T*{YKjFdc|S>z!_YONRoZ zFS&lx4C`eMInvAbm_#K@Cna6_BqBTjO{L|q?=9TYT0}%O% zNRZ83gGYvpnIpRS(u)x2Vc#`&fn^A2z!Qvjl3%)+@g?} zqSbh6qzViiQ(5j_ZBq$KY#)kye2uJq{N0|#HpD%$XQb4mBJ+u-0!E6(n^&y&v1|B9!TY4YxOdC4>8OXAi1V6cHuseQOeqvTF=xn-v0Ee zgqOGfpSAm98{JQ{q3j0(d&YA7eFFyWwK!Fz+nZ{!#O=>^Jv^yEB_NzS5uY2m?}K@5 z!_nrY@64c5KpUD)X%_M?qB^%Lif3ZlAa>Ar=drpZSti0#E86f2HS1+|xMJIy+q-sI zq~iRMe=zmJ8+Ul0BzqT0f+Z8saR>D)vY4!;Hx~y*4_HJh&j5u|oa}8k*adqpdGs>0 z1S`YU`;DZ^$Vp5_Y0eX)qY`%y0ZcyDRP3`yXhX>P+j-_v)Am<4jlouEEhltkjslVIKL?HR%gmquH29&Iq zSq6zox(9ucLshZ`5I>Rx^fB4mZq6VW0UBuC*@37v6Pv9y;)_{g`l2A#P5lJrWN zcH)_Gl2K%#P-4#)VbGN_4Yc_Illfj7T@R1sw2GOR(cOA}@gJHZ3_}b}QOB{WiKLxj zNvX@B-d74hboczCX5JzAuz)@fL>sR1g$47%H5Qy12B!NlxB{i3cFZ>#SHtp-#GoM0 zfgby3IVm`}mi#ndxL_EHuUA<{4%1y8$9>S~2kHKHoLUrvKa zE%nim_06j}b@I4a^#(b?xXPR1tbH5s`vdF)yo z`7t5*@#p&M=pFn9%n{_`s>tI;w|}qpT4wJ|*!7rDj%t-%i{zkp8i;KM5K!D1ts7%H zD47%{fVU+3H_m1D=|}HN@Nk$gYWaT^6K%gON;1+F;$GHig}VY+dX7iXmi_{UkLXzI zvG2D0Y8J7=$A0~Lj&eZBr^sZTH_O$J|A^_!FlSN2^?Cxdej5zR9xAQxr|kx!xxHj_ zx(>3P_w@9DM{u1o>GH|kS@TB%+xkxwq=*EAnL~GT&G|@6E;4WiVF1%}94F9Y{?otx zb3s%#JGg9l!Q9|zoTH0dGM~)KDV7>sOy7h1MAq-&JJh{J#a73WF|4iHw3eg=?sXeW z#(yt}AgmmmX|DWt3O;L8q*0n^UkS!shv_b0>(O@1JvMdGgP(rp9=M!KFp7bQz*aZ~ z!+S0iuD)6KRZ&kSitu|UR~RT*VXtYK(&l?g0KqUulRVdr(i2?!Wpy8|5ilmfUDLYY zKs_q0%nw()-$x$7|NKyv6y+j4Vn_CuEHj*7iIpIiI;><;OA))bq1tI};!lBNB?aV6 zu!X(R&o7mKF4c%XtW>Y`JUCm5=*!2NPpaNEflHe!5`iq)Gf78PzfAAZv?VjY@0q9) z_)uKYxoHcpi1w(k*`o!#6j}J~^UHH`vfG6U63jK{F~0EB+`Tq=!fmO9x38_&*MaAu z`Jt$cJ6=GyJ7k%Ln}tH;>6bU7_rn5x=o6JzGDMt8rB54%?RpQ<+38$P_fd)vO?DF< z8bSxtEb!DB&BYI_RUF){W^~I)R#$nyCx1c+>X{FRv{m7>!^%jS-{6C%orw!Nmg##xR zBG5*D8kDH9g!mZ7)<)w(dqsX>KwrfIPT}DGe#M@=#EFY`lIEPjp|Z{nNSRREpWm81 zl;DbnyngcpA*ko{sE4#8eAg8^SXGA;D6S-u-E5#7XBqnb3MHt~lL)lh#bh5u1U1*B zk7b5N3707vQ9m|OZ1RpJJ%ObUMzMnkR9MtVMM3bfm6!hGS9=Ise0^@%ydL7N6+boz z@}b2}=07}vl8T;EJAcF{#O7Z$oF{GT?qy#eJJBZyU&)9NTQ-M_8?3+Ewd1{lsQA5{{SN{U7P0jN8e&hY7q0d$M}mnXcYnMf8OKvD}eke2{*XQj+yn}53n}%ea~DTei(_sJ{#)flVM|J$m9uWF z`8i`$kmt8L!haxIemDl-15kEZ;#BL7Z3rdJ!N>L~Obq zGNOEzgpzrWzzbttSlQ;im;E&*@0pOD1Tlm^i%`skL=V>+Wc)0{%{`e zQI<%CCef-_5s;l0V67Y@%9b3vjp>l0dfAak$ZG?nf|HvG0^uoWD`W&aZq&~v`it6P zHEuUZRs*t1u_*aw|8VYXH-$7TpXCa#AtdoC{+>*%EQgnJBKCVc6a$Zi*Ap&h#7bdLF8?qKSS#UG>T%ocL_D7piWK#&U+b!qVw=XT6KEL<0Q)%uY z(qKM~J5h^^^Q;{u0yQrNUu?91&Exx<4PyX zf}`QNx5BLA3MYo^A+QFAQI}a;x~q$yh{bUI;XLZy_J=%zf}joY2Hm2Y*<6#XgH096 zBie5tfkK^%@AJP6=l@QaFxC7xuWmTk7lFibFCL}^{0ompmxyNy7UUt0UZ1a`0>$ed z2ilp6AGW@&JXFUq3oF>XQ8P_TpNFBYRFqeLi1Q&G z#W8fqY0a+O{zH2aL0twonpG7P(A&Ru%8bz4qd)}euEYFgs!>dPTRKqDMvS#-RUNgG zA1>GzHOUQ`#e%j(1QFNp;{c7lXf3mRv}z;>!t0U0mcPtr1=BzKRISQQ6aPPPUMI5t zRt+G|SftsO#AWsEPeTdeXX#O#K*rTDIumFax=o4EW#; z(Ldr`pS+_%!Zhn+r0J*ldcJbHr-;rt6qrN|a}_0en(v3^k$Q`h6pb|u%5!me{Ya5A2@oM7xq+!gylRmA8?q3MWDsy8`2G1)QsVz1&a>dR{9lT=m_58X$C4W2!E+^fRshQ z4j#;R7nbenRVb9m-*6;2?P@}vTy`TZ|HpZyb%kQp+75$T+iYcfe%5Zo8Yh&reD)X= zU38nq%TQpb&r8al<|MyKp;nHnUygBzV#0BedT&f%+AY379Qlc6to;$EBpF}YqQWdg z9MPNWH@~Eb9)6j;8*KD9qdDKsUlEG^Lp+I{vRaoR}S=g9?SRI8mKZ%7#<0#EL_4Z1bVm_MAexb7qK7I>8> zjQb8#kj9pt?V+L3h=5b<=LM~eSr``le=TQe9vfzyhwha-pC3}Iw&ofm8`ld=A$xHp z)x-}E*UCU=n~Rwj@~BQ@dUo`B=~Y>O1Gvzqi$W<$m+{wHLH6;yDgO~wEL)qjT;BUy zcJ-=jJJfxS;iKxMGP24?+D zfI=Zuh8RAbu%~RiuV4r%%N0z5Xm|x=79XyskBO3Buba!T(nwW=y;<6&-d5tj`_ZJ0 z&(B{x|DQMsVg6s@6XqBCzw#}A7eTsYI!#EC8!*MbXgb0*t4sOU=VXhT75nUG*O*QA1z*e;+-@U6u2P{-{x=vb9-4?P_F0#vuZ-nS@$2ysm#yT->qA) z(dnRh_GUl7F&mzN#(0BX;Fm&QK`R6&a38X|@5(9Dhi*SaFLEGnNdxarWz@B7Excbb zaPVK8+8g|15WM5A>Z!pqGq|J~J0lZe9m@D}riwYQ z^xZ^AQ=80x;!I|Q`K2}h;X2mI7;4#gU(NZBRy;hrvk)$|CJn8wA20{V518E42hC=T z3Ph)i)V{_Wo`pQN2E9Dr^0I$9_zvm+A!-+2wi8(?5Ps#M9F@2zsit+#Qr6W(jCs6iEdKdto9a6IPBNKf9S@*@x=o{gaw0G+r)0z1pRk2s9;WO24u~!cXwF zC42RD4&@cKJEA^WGtO2BnzmOks?>ktL|OkG2>bszwt0(xPd<3^N|_-G8s8@EX1-W; zS0&{H%c3`UZ~Z@U!b)23B!i3L8|$!VcC1^U;21mL>0Zno zWFI8)mRPYolnbc%b>Km!Ag87^V4^|zb*FlO3j2Gi`+p(jp_uMUK5HYi4=)xNI=9pB z`{hDOj4c|^MTj7S56tGqL4?Ypy-K;6+YVha4|FC+v5}O59GrRfp}X`ruroawNgB_@ zG)z-i>uKJ{4&;1*>><#9b+vND zj8{j-$mqrLV!>4FxM>Ec_GQ##vuBD*xBZM%*8DE;GA&_Pbm}cFDU>2~-^a-I?$KvL z5{+j1tEl@*3l>5FZS-aaA07#PXHoDlGm zr`p5ksA<&Dd8@bkHL|UcpBnwPWFc|dhdT;Z}{Sr)jbBwD}ayjHR6gRvqSQj!Q z>mXhBGcj=yTWUxlhRWl=yQIL0`JUZ`Z0S|AJaA%6*)z5K)gti$BOLoy?Z)0|D)-PzRyN^*uJSh zguaR)5(R%0kBNe1+X+$`@7vest;9{hrc>=8LQ7K-J5bL1-eRsc-T@^%q2aWx(dH=` zgs7|YR2KalHG!%#e^fbvREQ2Y2WPp>(kZpl#*{Qnc7Z%KJiXTUXue!bA3uVuJ zA^+Jgh5TY3!G-*j$U+(5ffoXi3x$>%fsDhGhw1|)mmh?H%jf%rXgCOgtbW(UrMaK1 zt6e^iVX|#lYAGYDYmLHH!WBR;*gd3(e8}cCsl>O$JXt*pQafp%<=&63tB0~T-f)b$ zmQU7qtw~&=NzbG^>gOBuE01>iO6%#~kYpvgY>wh?0>vBDe^~EdRq7-V?N^gni`{G}ZcZ@8WCcmQ1oDGDsbMA0g;)Z{&e|t@#SmhNV0iP8_7xWAAnyNI|3m){R% zLQsn8U47+P4&X`BAj&Q-7%Bip(s2g?v+Uvz-g_=72F@gN{V=x`%aoRu?)h33H|%~R zIA6Sm)cZ_GNGluFwAxca*#_>TX*$@qv9UcoS|03PH}8pxL_A>{{z9^smk@3?yEGdO z`XQ(=8<=+?PeV0&G3?lJj-#kle?$h9=%H*I`KGJ{xLxjX>5&87p8iNNa%bGBaCnhc zs7e6>&xEu{ADuMGMn#LMuNWT~D%3|2%RkO*hYj7p?3Ip)QTDqD?7uC%Z3jWekl41% zoX|-rMBVug8cc7+Q;o)3Lpn7!Ei}dmMCcSZ({EDP)mJyE{1xW4t(R@$Bcla_c6o(4 z0wM%G7vD26)pg=xDco42JgPM(ZCqa{g@sjBV@9}~F6S-78`f0+4v74RX{C}K%yGfo zfN*`)lbAt+b6Wg^KzQ5|f;7?x^}Zq$au6Z@Nn-r**jH15*Y4Q@&1%qk;?xj9Z`BUX z#e?*-&#%XxiK+2JTk%mm+tH^!F(*(n{14u;-l^5C;8)}a`)C`%*gyU*9dkT}fnKNO zLymDpW0a!#27#`!)*{Xe>#5aU0ueG;J`mwMrbDhb*0egjvx3CW0a0E45TUh*~Azf^mLR9Rmz~17+>6Uma^p$;`$#nsIIkg-n`%| zF*lHhr_zUeCL~Z5`IANblL(-1pc`^ zk*OA??^}^2z*828^p;p#`XA$K%_ywm_Mla!#wjnBma>bq=&FcEAEac-ocgnoRPu)7 zZ<`zcnSa<)6*-C{%qkTRo;_MdREm6X1Cse?@$SuKO^6S`4M9R00B<+WeRU=r_O7nJPX8IFa3;xiWBX(yp0A;%NT1fZ79Mx56(Udhe3Vs#>;G^yg( zlk58&o}@@KG(dQ7-5%6Q4V;P`~;k2Vc-*e8*i?VEm% z@0N}wE|xYbXJYOOT~g)7If2G?&QO&@~KR}i4+wZ$6a%C=;`UzqpmJK0D@0J8QY;)gjFVsR&*AN!42OST$?kn zceD4hILrfw1zes9$x?i^n%c>)bT{0lirr>&@0vt!#lgC$#gAV7J&L2`=3gfA!$I zuJ<|kdFlr4Ju`dOteIJ}Xvcefs+Vy2jwjB8A0>&wrw8E1?8yH^v_DTIPg(cGqX{4^uWUm}s#oTj_tKt9{|;stiE zZ2Z`ZQz%kN`+}UgTx=haL)dU-c@T}>Xs_WG$S};!0B@K-ML`2`JdtFQ)uNgS!B!)n zF@ASnvK{Js_K_~yXZ9lS@I>qrch;l<+EIVZQtnd>$KY z=W_c83A_5*V#LBpdd65g(OkJpKo1rgMrz%JRjXbUP;>kE)aaRRIwn1-VO6q)i~-Ok&zJAsr1=JWnY zsfo1Pxq}~+C|gbCi^qnarY*r_qO;PCQmt6FU51)Jzi+}>HyJ^tYvs2rG1)Fn2-CAo z16k#7Yg!^O;FBynWyUF~ei)BpW!&qtHh#l3N@|(A8bCf|2Q+QlhBFvE)+Q`^>9zjF z!om3>lZ6^xNT5BGErP>zCgmO%OeT3@P3LNW)Q|@9b zTdYyeagj+uM#jAqAb#;RFvkW=rgdp^X|tI*=^HvE@#A30&o%1_!XsAE7~K~aJkK(s z7xw;`lM-W)kbuI@>jd)RJ!p~e7lM3- zQC}2h*41mPV2J;I!yB8>hs8qg#waHIs4{{$q6U(Q!KOoiWn_qrAxictft zu1(b<*oH8u7-r*FH4PN`a&q!X6^LcMz^-A`_^&jV3@suKy$LwVfeYcoG zd3&nxfy@?@2w}6OBfXgY=A%<}&>sKy=W@-(Bcq-m2Dcd81pTb|^wrVjgR~9j^3Ztq zd&Flr*v>$6fX9@Hbu@{`s-6R>MhPrxXGup8Z4#xP6*W=Q()^sah^%dii7&@7x$*=9?}IPb0F0;-D_ZdWN}O3h zj}&~yYwEvD<&4(XYWr=F3u;DPPYd0@r&mGR66!R@mvv~GT*rRo0j`~vFeEN!JGC|< z*x%K}IffXkplUkj(slV7RP4L@ISJH^3O`8uUZ!zIOn^<>Lz$XX{@GQRx{Rz+(su+#1HI zozfSEpBkgFNXq4+G|(n}SRpAUCGppZ11f>_9idissztJ%l_%*^EY|cI5~%6NySeAr zC9i!^n?nFGQN48HRN6p_cwHa@w`Uc&y?o1L>>HCE$66c)or&u3b7tQ#5g88I5og`9 zqs1I4ZAhoQ2BXQwR+)fkNw!fuZci8p>9^{-`tGBv5a%J9Cawz8XWG|Zss?^+*8Tkc zu9b&93)KBqmX}ES(dmz#IP{72Gal`^pH#Ej@RW!sPy> zo-h)k(u9AQwpHBD8MP0EA#jcNBRYsiR;cNb4olPZe0cW0ZaIvL%NX-N=wLu{l28#`{B6BgMCow1=a^0wOI#-p+U^vUk z(Bh86hwWA1Xi_D{f3F ziM&NQiHFss7y;Tn+!nK2=6*PwxN05{*gdtXO6nbq4Z8yVNV+%P-Kjgg=+2vuG+d`hZ z{7^LckLI>s5PhGLvL85@qZP%+-$d4+-{T;bZBy2LzJe9snVkaMI70dQo#d6|PH`x{ ze%?vJ?Ns88HDMTvDCuO2!5u0gC|^$id)b3UtLDYV^Q~02aR$dLO4j|oVkPw9TbiqZ z^OyJ_rop+Ae%!A?P3mxXobptJ%34p(l=~#`P*q385!>%y-T=<`e-`fRvasf3sdk`c z@ZNI9;VB=(N$&cUsl*&j(t;QJ^rjM^I@OvCiMtC{-qb-AgGqy+K`iR0Oo$-~TaOF5 zo|@o-!Z2`{phiK*w}F2k=enA4c$F>WYG?f@LWxpNw)YK^uUSy>{!_%CPXGnCWM}%N zqEYIm4c%`h&u8*aRdV(#{}kMi9aBnmOisorG7G0+5sz)X1z&lZE1P^Y4s7p5 z0sWR=mWjHD8$o+c?ao_Z$meQq-XW%4)qeL6A z^Q#750LjX>pDXcASt*&?Mn3IPH%13xs)nFWdqC?ByV%DQ0um4s3x#lH%vNrf#;UDC z5ucCLk2;6c7c7#hqR0oeqeGU2ASwO!bI}GtlhMNw{ZFk-Ro|E6}*EnlKr*39iOR<-L)!W^ZEB>V2Ww5&TDoJM%U-I z!jJs(17?wCR`mBi)5u6gZ*{DDx><^UnCjs?>+#?1@RMu)U=D89*(h*Iu_93wgLm`C ztc4=4X9q4>TktH!MVc^B+hi9$l8Gup*O^*OlpR;9jk(`mOJM{}jsZt?UAxVj209CI zd|8q#q_ewc&c)>!hjT19Kw1`E*%M zU6v7vv?HSpx3yX588RLVp-D;C12H5T`(JC)Lzul+eXeZrW^rlP|D)n7YPsOV;lz(l z)L%RXVUGnXfhls#Tx!E+r=H!>ngM2~u&b~xW;T1r8AI~?lOeci6rh?$OlC#>yHY7j zZ)lsrR}MSzeOBLgJxEkCU3*nFIDNlaf~<)th}ov(gR^;_DhKCZP{h< zx-prLDOO(J8qGt7X3K z3EVZwcKT~guk#E9e9Gv=2fYL683sM6Wq35B1Ysk#ysc^5f6&#L0Kqt_hu(r@POW}g z&+T;EMp;*zC>wS_QDtb57s0L17%G@zlBU!Mx^UE(uMnViTwl`j$4}oLPmIkqxqQGx zm7U7>_(x2E2$cx<66?)XSVT4ldzf!|Uj=TUS}V^nO2P74jyr&ssLvc^-8UIO3RpJ% zrm~>Q4a8OZj@vCGIe3y@=J)g`fys3LMBq7^>IipgIecEV;e1lbmarwdbB^|2wZ>r0 z^Ob+@0hQ-}M#Y(d#qq2>(R%3|LFpWQG~U#{j9Wn{QGG>Ug%yg7CHuK2H4%{dZ|@s*H~#M35mt@GL4gP%gHlVdo$6#;@|j|hT8#2(cnDAf z)F>_L|H_w-a=cxJ5t%u>b#N?ckV0f((Y2WWxHp6G6j+0K<^~At`@Y!)B)8gpLYEc0 zCP`NjlUMgkX7&!=(65XDeZwRYUkOHV8eke$+}9oX%>9*KaMpwljEW_rAs4$~6+Q+X z68+88-*X_jd+?xBIFEPFvJhvrL&A6tUl#YMf#H^y9Og|QFpEc9)~ar|UEvyU9S8(_<71@8pl=GJ^ zondak+(iVrutW;U=ZUl2G>DzC2-6=dX9_mS6b7t1voJ)Bph3+A|Am}#0j6Iof{vHC zz;}Fn8+>6$(Ki$gjKn-P-kSs=(l5h|VYL`{<$$Cwf16cIkmnM6Kn)ibN!4fcg^3s1 zjraEGRSSiW4SHO`L}Ky}e#W$1I)aBS)SQXI&>e}Qjgsa@&&9nNK-T3%_eFxZEqcCL_C2aQ|eMY zh^sK`dCIg@g1^^+#EVf)04qHB>2F#D zbpH`j{vRYq)-Ao)liZMk&rq0xibX`C-f z|0;L;)>$y)ujWcGCDG=>d4$U>hY^(DDHX?aRgbB}1P>K2B^~EZI|3E5XINPVJ+w<@ z8hSelu%0?N((udMDv+tMx2i%h+b*)8jop92l=+uwJ#hZkalwg7RsP}qCe8K@agYld z>C4D{Ss9#;v$WMQpwNPpz}yTMX-aEl?W_WK=yu{v|7gcIEn~{4!T{T<%QYGBeEmqe0N&7`dqsAPL zKG43bpUNlxCH54#_pRlrQS&#!#|aR2-js>0W}MLWZBF<7U?NS^pscKR@iu4@|&%j1)t@ z-UrM{Nig8cYb)U9-TcwC{zPB8S$0$f2dyW{f5dFM!)d z5?79;%2)>zX?4O|60&&sQ>UJNxyxY^yT0Cmja+sq>G-j7)JY|aN8ukN3-woTmT#R< z8QWjra>k@pHxNK*5ZWWfE{8@K4)Z++3a)H7^w3Sbg3*4b@xsQmf{S7H``WD^Wur&Y$Bu@_l zvW4x+K~)U27?>c{qMpOg9nX*FVDufA@r;Uq0VH7gf&WWNMSqQHVc$+~w>&v}tQ9fp ziAX=};Txz??96p-#STRszEd=S$pb>k>x`h62StA;3AcEDE@cD0^k_tej@SDQwlMWH zXaWg1z6qoLy5u64(O;Of%C5$xf9GwhhUw@?@yAtZ`=_g`v|+)2nI=D~WU*%l;=wg) zuVh5?d%^WTy8~odDL5>bMr30h>cD)#w`rbOm ziV`wkYjQA=bUq+7+dRSKnGbY<8?ju2@P=w`SPUd=pG3@y5O%dVD>itg8a$8-6p@ z5!&OFk(-DM_w9P#?Z(nFPzk z=-ZGSDEnlr{VUu)u($tDm_WUzznNyD>-{6lPC`ykUNLR*G0bqPY}SXq;<=z7WxNbB zV3h`jXfF@lgqn8ic@A7ifAmnITW2kSo1~dE?z2OE+TsMs`yVm6{AG%`_4_CV(~ZoN zA8*FC7ygm@IpfRJu{V4ej)l*y6mx)7krb4)`lt2ht_qftYK{4R=d=62bm#h)=?CIzE!RMM0qnkUPhl<<0^V7G76r}CpwK`86K*S}9dM0Y zejM?)?&kCpr_(Esugo}2u%^sh0e32g@Cn(fUW}moQ2q-hH-PEa!h(i}O`7b3hM!

c+5sbp?F4}SeJ2G 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

+electrs.toml + +``` +type error message here +``` + +
+ +Environment variables: `ELECTRS_X=Y;...` +Arguments: `--foo` + +**System running electrs** + - Deployment method: manual (which guide did you follow?)/native OS package/Docker + - OS name and version (name of distribution and version in case of Linux) + +**Electrum client** +Client name (if not upstream desktop Electrum) and version: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/build_problem.md b/.github/ISSUE_TEMPLATE/build_problem.md new file mode 100644 index 0000000..5f9eff2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/build_problem.md @@ -0,0 +1,50 @@ +--- +name: Build problem +about: Building of electrs failed +title: 'Build:' +labels: bug, build +assignees: Kixunil + +--- + + + +**Have you read the documentation?** +Yes. (Please, read usage.md first if you did not.) + +**Did you double-check that you installed all dependencies?** +Yes. (Please, double check the dependencies if you didn't.) + +**Which command failed?** +`cargo build` + +**What was the error message?** + +
+Error message + +``` +type error message here +``` + +
+ +**System** +OS name and version: (If Linux, the distribution name and version) +rustc version: (run `rustc --version`) +cargo version: (run `cargo --version`; not guaranteed to be same as rustc version!) + +**Compilation** +Linking: static/dynamic +Cross compilation: yes/no +Target architecture: (uname -m on Linux if not cross-compiling) + +**Aditional context** +Any additional information that seems to be relevant. diff --git a/.github/ISSUE_TEMPLATE/config_problem.md b/.github/ISSUE_TEMPLATE/config_problem.md new file mode 100644 index 0000000..8db2a89 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config_problem.md @@ -0,0 +1,46 @@ +--- +name: Configuration problem +about: The configuration behaves unexpectedly +title: 'Config:' +labels: bug +assignees: Kixunil + +--- + + + +**Have you read the documentation?** +Yes. (Please, read usage.md first if you did not.) + +**How did you configure electrs?** + + + +
+electrs.toml + +``` +type error message here +``` + +
+ +Environment variables: `ELECTRS_X=Y;...` +Arguments: `--foo` + +**Debug output of configuration** +``` +Enter the debug output of configuration shown at start of electrs +``` + +**Expected behavior** +How did you expect `electrs` to be configured + +**Actual behavior** +How does `electrs` behave? diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..86a3e23 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Generic feature request +title: 'Feature:' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/README.md b/README.md index 2cef7d1..faa0553 100644 --- a/README.md +++ b/README.md @@ -14,21 +14,28 @@ allowing the user to keep real-time track of his balances and his transaction hi Since it runs on the user's own machine, there is no need for the wallet to communicate with external Electrum servers, thus preserving the privacy of the user's addresses and balances. + +## Usage + +**Please prefer to use OUR usage guide!** + +External guides such as RaspiBolt can be out-of-date and have various problems. +At least double-check that the guide you're using is actively maintained. +If you can't use our guide ask about what you don't understand or consider using automated deployments. + +See [here](doc/usage.md) for installation, build and usage instructions. + ## Features * Supports Electrum protocol [v1.4](https://electrumx-spesmilo.readthedocs.io/en/latest/protocol.html) * Maintains an index over transaction inputs and outputs, allowing fast balance queries - * Fast synchronization of the Bitcoin blockchain (~2 hours for ~187GB @ July 2018) on [modest hardware](https://gist.github.com/romanz/cd9324474de0c2f121198afe3d063548) - * Low index storage overhead (~20%), relying on a local full node for transaction retrieval + * Fast synchronization of the Bitcoin blockchain (~4 hours for ~336GB @ August 2021) using HDD storage. + * Low index storage overhead (~10%), relying on a local full node for transaction retrieval * Efficient mempool tracker (allowing better fee [estimation](https://github.com/spesmilo/electrum/blob/59c1d03f018026ac301c4e74facfc64da8ae4708/RELEASE-NOTES#L34-L46)) * Low CPU & memory usage (after initial indexing) * [`txindex`](https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch03.asciidoc#txindex) is not required for the Bitcoin node * Uses a single [RocksDB](https://github.com/spacejam/rust-rocksdb) database, for better consistency and crash recovery -## Usage - -See [here](doc/usage.md) for installation, build and usage instructions. - ## Index database The database schema is described [here](doc/schema.md). diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c2c52ed..445b2e5 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,13 @@ +# 0.9.0 (TBD) + +* Fix incorrect ordering of same-block transactions (#297) +* Change DB index format and use Zstd compression (instead of Snappy) +* Don't use bitcoind JSON RPC for fetching blocks (#373) +* Use p2p for block fetching (instead of reading `blk*.dat` files) +* Support Electrum JSON RPC batching and errors +* Use `rust-bitcoincore-rpc` crate +* Increase default `index_lookup_limit` to 200 + # 0.8.5 (1 July 2020) * Add a 'blocks_dir' option (@darosior) diff --git a/doc/config_example.toml b/doc/config_example.toml new file mode 100644 index 0000000..f73497b --- /dev/null +++ b/doc/config_example.toml @@ -0,0 +1,29 @@ +# DO NOT EDIT THIS FILE DIRECTLY - COPY IT FIRST! +# If you edit this, you will cry a lot during update and will not want to live anymore! + +# This is an EXAMPLE of how configuration file should look like. +# Do NOT blindly copy this and expect it to work for you! +# If you don't know what you're doing consider using automated setup or ask an experienced friend. + +# This example contains only the most important settings. +# See docs or electrs man page for advanced settings. + +# File where bitcoind stores the cookie, usually file .cookie in its datadir +cookie_file = "/var/run/bitcoin-mainnet/cookie" + +# The listening address of bitcoind, port is usually 8332 +daemon_rpc_addr = "127.0.0.1:8332" + +# Directory where the index should be stored. It should have at least 70GB of free space. +db_dir = "/some/fast/storage/with/big/size" + +# bitcoin means mainnet. Don't set to anything else unless you're a developer. +network = "bitcoin" + +# The address on which electrs should listen. Warning: 0.0.0.0 is probably a bad idea! +# Tunneling is the recommended way to access electrs remotely. +electrum_rpc_addr = "127.0.0.1:50001" + +# How much information about internal workings should electrs print. Increase before reporting a bug. +verbose = 2 + diff --git a/doc/schema.md b/doc/schema.md index 8397748..64b6a07 100644 --- a/doc/schema.md +++ b/doc/schema.md @@ -1,6 +1,7 @@ # Index Schema -The index is stored at a single RocksDB database using the following column families: +The index is stored at a single RocksDB database using the following column families. +Most of the data is stored in key-only DB rows (i.e. having empty values). ## Transaction outputs' index (`funding`) @@ -29,3 +30,24 @@ In order to save storage space, we map the 8-byte transaction ID prefix to its c Note that this mapping allows us to use `getrawtransaction` RPC to retrieve actual transaction data from without `-txindex` enabled (by explicitly specifying the [blockhash](https://github.com/bitcoin/bitcoin/commit/497d0e014cc79d46531d570e74e4aeae72db602d)). + +## Headers (`headers`) + +For faster loading, we store all block headers in RocksDB: + +| Serialized header | +| ----------------------- | +| `header as BlockHeader` | + +In addition, we also store the chain tip: + +| Key || Value | +| --- || ------------------------ | +| `T` || `blockhash as BlockHash` | + +## Configuration (`config`) + +| Key || Value | +| --- || --------------------------- | +| `C` || `serialized config as JSON` | + diff --git a/doc/usage.md b/doc/usage.md index eaeda8d..b1364ed 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -7,7 +7,7 @@ Note for Raspberry Pi 4 owners: the old versions of OS/toolchains produce broken binaries. Make sure to use latest OS! (see #226) -Install [recent Rust](https://rustup.rs/) (1.48.0+, `apt install cargo` is preferred for Debian 10), +Install [recent Rust](https://rustup.rs/) (1.41.1+, `apt install cargo` is preferred for Debian 10), [latest Bitcoin Core](https://bitcoincore.org/en/download/) (0.21+) and [latest Electrum wallet](https://electrum.org/#download) (4.0+). @@ -34,11 +34,11 @@ The advantages of dynamic linking: * Cross compilation is more reliable * If another application is also using `rocksdb`, you don't store it on disk and in RAM twice -If you decided to use dynamic linking, you will also need to install the library. -On Debian: +If you decided to use dynamic linking, you will also need to install the library ([6.11.4 release](https://github.com/facebook/rocksdb/releases/tag/v6.11.4) is required). +On [Debian 11 (bullseye)](https://packages.debian.org/bullseye/librocksdb-dev) and [Ubuntu 21.04 (hirsute)](https://packages.ubuntu.com/hirsute/librocksdb-dev): ```bash -$ sudo apt install librocksdb-dev +$ sudo apt install librocksdb-dev=6.11.4-3 ``` #### Preparing for cross compilation @@ -139,7 +139,7 @@ $ mkdir db $ docker run --network host \ --volume $HOME/.bitcoin:/home/user/.bitcoin:ro \ --volume $PWD/db:/home/user/db \ - --env ELECTRS_VERBOSE=4 \ + --env ELECTRS_VERBOSE=2 \ --env ELECTRS_TIMESTAMP=true \ --env ELECTRS_DB_DIR=/home/user/db \ --rm -i -t electrs-app @@ -150,7 +150,7 @@ If not using the host-network, you probably want to expose the ports for electrs ```bash $ docker run --volume $HOME/.bitcoin:/home/user/.bitcoin:ro \ --volume $PWD/db:/home/user/db \ - --env ELECTRS_VERBOSE=4 \ + --env ELECTRS_VERBOSE=2 \ --env ELECTRS_TIMESTAMP=true \ --env ELECTRS_DB_DIR=/home/user/db \ --env ELECTRS_ELECTRUM_RPC_ADDR=0.0.0.0:50001 \ @@ -180,7 +180,7 @@ And two disadvantages: * It's currently not trivial to independently verify the built packages, so you may need to trust the author of the repository. The build is now deterministic but nobody verified it independently yet. * The repository is considered beta. - electrs` seems to work well so far but was not tested heavily. + electrs seems to work well so far but was not tested heavily. The author of the repository is also a contributor to `electrs` and appreciates [bug reports](https://github.com/Kixunil/cryptoanarchy-deb-repo-builder/issues), [test reports](https://github.com/Kixunil/cryptoanarchy-deb-repo-builder/issues/61), and other contributions. @@ -195,6 +195,7 @@ Pruning must be turned **off** for `electrs` to work. `txindex` is allowed but unnecessary for `electrs`. However, you might still need it if you run other services (e.g.`eclair`). The option `maxconnections` (if used) should be set to 12 or more for bitcoind to accept inbound p2p connections. +Note that setting `maxuploadtarget` may cause p2p-based sync to fail - so consider using `-whitelist=download@127.0.0.1` to disable the limit for local p2p connections. The highly recommended way of authenticating `electrs` is using cookie file. It's the most [secure](https://github.com/Kixunil/security_writings/blob/master/cookie_files.md) and robust method. @@ -253,47 +254,58 @@ Please read upgrade notes if you're upgrading to a newer version. ### Electrs usage -First index sync should take ~1.5 hours (on a dual core Intel CPU @ 3.3 GHz, 8 GB RAM, 1TB WD Blue HDD): +First index sync should take ~4 hours for ~336GB @ August 2021 (on a dual core Intel CPU @ 3.3 GHz, 8 GB RAM, 1TB WD Blue HDD): ```bash -$ ./target/release/electrs -vvv --timestamp --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" -2018-08-17T18:27:42 - INFO - NetworkInfo { version: 179900, subversion: "/Satoshi:0.17.99/" } -2018-08-17T18:27:42 - INFO - BlockchainInfo { chain: "main", blocks: 537204, headers: 537204, bestblockhash: "0000000000000000002956768ca9421a8ddf4e53b1d81e429bd0125a383e3636", pruned: false, initialblockdownload: false } -2018-08-17T18:27:42 - DEBUG - opening DB at "./db/mainnet" -2018-08-17T18:27:42 - DEBUG - full compaction marker: None -2018-08-17T18:27:42 - INFO - listing block files at "/home/user/.bitcoin/blocks/blk*.dat" -2018-08-17T18:27:42 - INFO - indexing 1348 blk*.dat files -2018-08-17T18:27:42 - DEBUG - found 0 indexed blocks -2018-08-17T18:27:55 - DEBUG - applying 537205 new headers from height 0 -2018-08-17T19:31:01 - DEBUG - no more blocks to index -2018-08-17T19:31:03 - DEBUG - no more blocks to index -2018-08-17T19:31:03 - DEBUG - last indexed block: best=0000000000000000002956768ca9421a8ddf4e53b1d81e429bd0125a383e3636 height=537204 @ 2018-08-17T15:24:02Z -2018-08-17T19:31:05 - DEBUG - opening DB at "./db/mainnet" -2018-08-17T19:31:06 - INFO - starting full compaction -2018-08-17T19:58:19 - INFO - finished full compaction -2018-08-17T19:58:19 - INFO - enabling auto-compactions -2018-08-17T19:58:19 - DEBUG - opening DB at "./db/mainnet" -2018-08-17T19:58:26 - DEBUG - applying 537205 new headers from height 0 -2018-08-17T19:58:27 - DEBUG - downloading new block headers (537205 already indexed) from 000000000000000000150d26fcc38b8c3b71ae074028d1d50949ef5aa429da00 -2018-08-17T19:58:27 - INFO - best=000000000000000000150d26fcc38b8c3b71ae074028d1d50949ef5aa429da00 height=537218 @ 2018-08-17T16:57:50Z (14 left to index) -2018-08-17T19:58:28 - DEBUG - applying 14 new headers from height 537205 -2018-08-17T19:58:29 - INFO - RPC server running on 127.0.0.1:50001 +$ du -ch ~/.bitcoin/blocks/blk*.dat | tail -n1 +336G total + +$ ./target/release/electrs -vv --timestamp --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" +Config { network: Bitcoin, db_path: "./db/bitcoin", daemon_dir: "/home/user/.bitcoin", daemon_auth: CookieFile("/home/user/.bitcoin/.cookie"), daemon_rpc_addr: V4(127.0.0.1:8332), daemon_p2p_addr: V4(127.0.0.1:8333), electrum_rpc_addr: V4(127.0.0.1:50001), monitoring_addr: V4(127.0.0.1:4224), wait_duration: 10s, index_batch_size: 10, index_lookup_limit: 100, ignore_mempool: false, server_banner: "Welcome to electrs 0.9.0 (Electrum Rust Server)!", args: [] } +[2021-08-17T18:48:40.054Z INFO electrs::metrics::metrics_impl] serving Prometheus metrics on 127.0.0.1:4224 +[2021-08-17T18:48:40.944Z INFO electrs::db] "./db/bitcoin": 0 SST files, 0 GB, 0 Grows +[2021-08-17T18:48:41.075Z INFO electrs::index] indexing 2000 blocks: [1..2000] +[2021-08-17T18:48:41.610Z INFO electrs::chain] chain updated: tip=00000000dfd5d65c9d8561b4b8f60a63018fe3933ecb131fb37f905f87da951a, height=2000 +[2021-08-17T18:48:41.623Z INFO electrs::index] indexing 2000 blocks: [2001..4000] +[2021-08-17T18:48:42.178Z INFO electrs::chain] chain updated: tip=00000000922e2aa9e84a474350a3555f49f06061fd49df50a9352f156692a842, height=4000 +[2021-08-17T18:48:42.188Z INFO electrs::index] indexing 2000 blocks: [4001..6000] +[2021-08-17T18:48:42.714Z INFO electrs::chain] chain updated: tip=00000000dbbb79792303bdd1c6c4d7ab9c21bba0667213c2eca955e11230c5a5, height=6000 +[2021-08-17T18:48:42.723Z INFO electrs::index] indexing 2000 blocks: [6001..8000] +[2021-08-17T18:48:43.235Z INFO electrs::chain] chain updated: tip=0000000094fbacdffec05aea9847000522a258c269ae37a74a818afb96fc27d9, height=8000 +[2021-08-17T18:48:43.246Z INFO electrs::index] indexing 2000 blocks: [8001..10000] +[2021-08-17T18:48:43.768Z INFO electrs::chain] chain updated: tip=0000000099c744455f58e6c6e98b671e1bf7f37346bfd4cf5d0274ad8ee660cb, height=10000 +<...> +[2021-08-17T22:11:20.139Z INFO electrs::chain] chain updated: tip=00000000000000000002a23d6df20eecec15b21d32c75833cce28f113de888b7, height=690000 +[2021-08-17T22:11:20.157Z INFO electrs::index] indexing 2000 blocks: [690001..692000] +[2021-08-17T22:12:16.944Z INFO electrs::chain] chain updated: tip=000000000000000000054dab4b85860fcee5808ab7357eb2bb45114a25b77380, height=692000 +[2021-08-17T22:12:16.957Z INFO electrs::index] indexing 2000 blocks: [692001..694000] +[2021-08-17T22:13:11.764Z INFO electrs::chain] chain updated: tip=00000000000000000003f5acb5ec81df7c98c16bc8d89bdaadd4e8965729c018, height=694000 +[2021-08-17T22:13:11.777Z INFO electrs::index] indexing 2000 blocks: [694001..696000] +[2021-08-17T22:14:05.852Z INFO electrs::chain] chain updated: tip=0000000000000000000dfc81671ac5a22d8751f9c1506689d3eaceaef26470b9, height=696000 +[2021-08-17T22:14:05.855Z INFO electrs::index] indexing 295 blocks: [696001..696295] +[2021-08-17T22:14:15.557Z INFO electrs::chain] chain updated: tip=0000000000000000000eceb67a01c81c65b538a7b3729f879c6c1e248bb6577a, height=696295 +[2021-08-17T22:14:21.578Z INFO electrs::db] starting config compaction +[2021-08-17T22:14:21.623Z INFO electrs::db] starting headers compaction +[2021-08-17T22:14:21.667Z INFO electrs::db] starting txid compaction +[2021-08-17T22:22:27.009Z INFO electrs::db] starting funding compaction +[2021-08-17T22:38:17.104Z INFO electrs::db] starting spending compaction +[2021-08-17T22:55:11.785Z INFO electrs::db] finished full compaction +[2021-08-17T22:55:15.835Z INFO electrs::server] serving Electrum RPC on 127.0.0.1:50001 +[2021-08-17T22:55:25.837Z INFO electrs::index] indexing 7 blocks: [696296..696302] +[2021-08-17T22:55:26.120Z INFO electrs::chain] chain updated: tip=0000000000000000000059e97dea0b0b9ebf4ac1fd66726b339fe1c9683de656, height=696302 +[2021-08-17T23:02:03.453Z INFO electrs::index] indexing 1 blocks: [696303..696303] +[2021-08-17T23:02:03.691Z INFO electrs::chain] chain updated: tip=000000000000000000088107c337bf315e2db1e406c50566bd765f04a7e459b6, height=696303 ``` You can specify options via command-line parameters, environment variables or using config files. See the documentation above. -Note that the final DB size should be ~20% of the `blk*.dat` files, but it may increase to ~35% at the end of the inital sync (just before the [full compaction is invoked](https://github.com/facebook/rocksdb/wiki/Manual-Compaction)). +Note that the final DB size should be ~10% of the `blk*.dat` files, but it may increase to ~20% at the end of the inital sync (just before the [full compaction is invoked](https://github.com/facebook/rocksdb/wiki/Manual-Compaction)). -If initial sync fails due to `memory allocation of xxxxxxxx bytes failedAborted` errors, as may happen on devices with limited RAM, try the following arguments when starting `electrs`. -It should take roughly 18 hours to sync and compact the index on an ODROID-HC1 with 8 CPU cores @ 2GHz, 2GB RAM, and an SSD using the following command: - -```bash -$ ./target/release/electrs -vvvv --index-batch-size=10 --jsonrpc-import --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" -``` +It should take roughly 18 hours to sync and compact the index on an ODROID-HC1 with 8 CPU cores @ 2GHz, 2GB RAM, and an SSD using the command above. The index database is stored here: ```bash $ du db/ -38G db/mainnet/ +30G db/mainnet/ ``` See below for [extra configuration suggestions](https://github.com/romanz/electrs/blob/master/doc/usage.md#extra-configuration-suggestions) that you might want to consider. @@ -413,7 +425,7 @@ After=bitcoind.service [Service] WorkingDirectory=/home/bitcoin/electrs -ExecStart=/home/bitcoin/electrs/target/release/electrs --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" +ExecStart=/home/bitcoin/electrs/target/release/electrs -vv --db-dir ./db --electrum-rpc-addr="127.0.0.1:50001" User=bitcoin Group=bitcoin Type=simple @@ -448,20 +460,36 @@ You can invoke any supported RPC using `netcat`, for example: ``` $ echo '{"jsonrpc": "2.0", "method": "server.version", "params": ["", "1.4"], "id": 0}' | netcat 127.0.0.1 50001 -{"id":0,"jsonrpc":"2.0","result":["electrs 0.8.6","1.4"]} +{"id":0,"jsonrpc":"2.0","result":["electrs 0.9.0","1.4"]} ``` For more complex tasks, you may need to convert addresses to [script hashes](https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html#script-hashes) - see -[contrib/addr.py](https://github.com/romanz/electrs/blob/master/contrib/addr.py) for getting an address balance: +[contrib/addr.py](https://github.com/romanz/electrs/blob/master/contrib/addr.py) for getting an address balance and history: ``` -$ ./contrib/addr.py 144STc7gcb9XCp6t4hvrcUEKg9KemivsCR # sample address from block #640699 -144STc7gcb9XCp6t4hvrcUEKg9KemivsCR has {'confirmed': 12652436, 'unconfirmed': 0} satoshis +$ ./contrib/history.sh 144STc7gcb9XCp6t4hvrcUEKg9KemivsCR +[2021-08-18 13:56:40.254317] INFO: electrum: connecting to localhost:50001 +[2021-08-18 13:56:40.574461] INFO: electrum: subscribed to 1 scripthashes +[2021-08-18 13:56:40.645072] DEBUG: electrum: 0.00000 mBTC (total) +[2021-08-18 13:56:40.710279] INFO: electrum: got history of 2 transactions +[2021-08-18 13:56:40.769064] INFO: electrum: loaded 2 transactions +[2021-08-18 13:56:40.835569] INFO: electrum: loaded 2 header timestamps +[2021-08-18 13:56:40.900560] INFO: electrum: loaded 2 merkle proofs ++------------------------------------------------------------------+----------------------+--------+---------------+--------------+--------------+ +| txid | block timestamp | height | confirmations | delta (mBTC) | total (mBTC) | ++------------------------------------------------------------------+----------------------+--------+---------------+--------------+--------------+ +| 34b6411d004f279622d0a45a4558746e1fa74323c5c01e9c0bb0a3277781a0d0 | 2020-07-25T08:33:57Z | 640699 | 55689 | 126.52436 | 126.52436 | +| e58916ca945639c657de137b30bd29e213e4c9fc8e04652c1abc2922909fb8fd | 2020-07-25T21:20:35Z | 640775 | 55613 | -126.52436 | 0.00000 | ++------------------------------------------------------------------+----------------------+--------+---------------+--------------+--------------+ +[2021-08-18 13:56:40.902677] INFO: electrum: tip=00000000000000000009d7590d32ca52ad0b8a4cdfee43e28e6dfcd11cafeaac, height=696387 @ 2021-08-18T13:47:19Z ``` ## Upgrading +> Note that in 0.9.0 we have changed the RocksDB index format, so you may get an error when reading an older DB. +> In this case, consider moving the old DB directory and perform a re-sync of the blockchain. + > **If you're upgrading from version 0.8.7 to a higher version and used `cookie` option you should change your configuration!** > The `cookie` option was deprecated and **will be removed eventually**! > If you had actual cookie (from `~/bitcoin/.cookie` file) specified in `cookie` option, this was wrong as it wouldn't get updated when needed. @@ -496,8 +524,8 @@ If a new version of `electrs` is not yet in the package system, try wait a few d If you've deleted it, you need to `git clone` again. 2. `git checkout master` 3. `git pull` -4. Strongly recommended: `git verify-tag v0.8.6` (fix the version number if we've forgotten to update this docs ;)) should show "Good signature from 15C8 C357 4AE4 F1E2 5F3F 35C5 87CA E5FA 4691 7CBB" -5. `git checkout v0.8.6` +4. Strongly recommended: `git verify-tag v0.9.0` (fix the version number if we've forgotten to update the docs ;)) should show "Good signature from 15C8 C357 4AE4 F1E2 5F3F 35C5 87CA E5FA 4691 7CBB" +5. `git checkout v0.9.0` 6. If you used static linking: `cargo build --locked --release`. If you used dynamic linking `ROCKSDB_INCLUDE_DIR=/usr/include ROCKSDB_LIB_DIR=/usr/lib cargo build --locked --no-default-features --release`. If you don't remember which linking you used, you probably used static. From 89bca4900d871261995091200438adced2f5adee Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 13 Sep 2021 22:40:19 +0300 Subject: [PATCH 098/113] Use latest Bitcoin Core release (22.0) in CI --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 48f3350..f58002d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ FROM updated as bitcoin-build # Download RUN apt-get install -qqy wget WORKDIR /build/bitcoin -ARG BITCOIND_VERSION=0.21.1 +ARG BITCOIND_VERSION=22.0 RUN wget -q https://bitcoincore.org/bin/bitcoin-core-$BITCOIND_VERSION/bitcoin-$BITCOIND_VERSION-x86_64-linux-gnu.tar.gz RUN tar xvf bitcoin-$BITCOIND_VERSION-x86_64-linux-gnu.tar.gz RUN mv -v bitcoin-$BITCOIND_VERSION/bin/bitcoind . From 3f41cdaa55cb2696cf6d5c47b2c55b074200b5d6 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 14 Sep 2021 21:32:28 +0300 Subject: [PATCH 099/113] Add snappy to RocksDB features It would be useful to detect old DBs: https://github.com/romanz/electrs/pull/477#discussion_r707600255 RocksDB compilation time increases 1m03s -> 1m07s (+6%). Total electrs build time is 2m05s. --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f9ec76e..96a7561 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,9 @@ rev = "06ac9fa3e834413f7afeaed322cf8098d876e4a0" git = "https://github.com/romanz/rust-rocksdb" rev = "2023b18a7b83fc47b5bc950b5322a2284b771162" default-features = false -features = ["zstd"] +# ZSTD is used for data compression +# Snappy is only for checking old DB +features = ["zstd", "snappy"] [build-dependencies] configure_me_codegen = "0.4" From 9e77534e3326c48ad4114e088921532b9c7f37af Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 15 Sep 2021 17:50:09 +0300 Subject: [PATCH 100/113] Use docker only for integration tests Run clippy/fmt/unittests separately. --- Dockerfile | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index f58002d..2ee262a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,15 +2,10 @@ FROM rust:1.41.1-slim as electrs-build RUN apt-get update RUN apt-get install -qq -y clang cmake -RUN rustup component add rustfmt clippy -# Build, test and install electrs +# Install electrs WORKDIR /build/electrs COPY . . -RUN cargo fmt -- --check -RUN cargo clippy -RUN cargo build --locked --release --all -RUN cargo test --locked --release --all RUN cargo install --locked --path . FROM debian:buster-slim as updated From ea4d9b4476d7855dc21f915653eda0b631ae1519 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 15 Sep 2021 17:58:11 +0300 Subject: [PATCH 101/113] Add a build job to GitHub Actions --- .github/workflows/rust.yml | 40 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b803d59..1a6ac91 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -3,8 +3,44 @@ name: electrs on: [push, pull_request] jobs: - electrs: - name: Electrum Integration Test + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + components: rustfmt, clippy + profile: minimal + + - name: Format + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --locked --all + + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: --locked --all + + - name: Clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + + integration: + name: Integration runs-on: ubuntu-latest steps: - name: Checkout From 66b7b8bad8e57bd4d78384e628cfc011dc868b2d Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 15 Sep 2021 19:23:49 +0300 Subject: [PATCH 102/113] Deny warnings in clippy --- .github/workflows/rust.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1a6ac91..e9c6fb4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -38,6 +38,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy + args: -- -D warnings integration: name: Integration From fb7e9c576c5b7e8f464df90160d6b38d8f1392fb Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 15 Sep 2021 11:29:03 +0300 Subject: [PATCH 103/113] Cache amounts in status::TxEntry --- src/status.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/status.rs b/src/status.rs index 9540e8b..1d48482 100644 --- a/src/status.rs +++ b/src/status.rs @@ -22,8 +22,13 @@ use crate::{ /// Given a scripthash, store relevant inputs and outputs of a specific transaction struct TxEntry { txid: Txid, - outputs: Vec, // relevant funded output indices - spent: Vec, // relevant spent outpoints + outputs: Vec, // relevant funded outputs and their amounts + spent: Vec, // relevant spent outpoints +} + +struct TxOutput { + index: u32, + value: Amount, } impl TxEntry { @@ -426,16 +431,24 @@ impl ScriptHashStatus { } } -fn make_outpoints<'a>(txid: &'a Txid, outputs: &'a [u32]) -> impl Iterator + 'a { - outputs.iter().map(move |vout| OutPoint::new(*txid, *vout)) +fn make_outpoints<'a>( + txid: &'a Txid, + outputs: &'a [TxOutput], +) -> impl Iterator + 'a { + outputs + .iter() + .map(move |out| OutPoint::new(*txid, out.index)) } -fn filter_outputs(tx: &Transaction, scripthash: &ScriptHash) -> Vec { +fn filter_outputs(tx: &Transaction, scripthash: &ScriptHash) -> Vec { let outputs = tx.output.iter().zip(0u32..); outputs .filter_map(move |(txo, vout)| { if ScriptHash::new(&txo.script_pubkey) == *scripthash { - Some(vout) + Some(TxOutput { + index: vout, + value: Amount::from_sat(txo.value), + }) } else { None } From 538d460f2589c23a95ac937ceff180f11dfc1176 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 15 Sep 2021 14:40:44 +0300 Subject: [PATCH 104/113] Log unconfirmed balance at history.py --- contrib/history.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contrib/history.py b/contrib/history.py index 7e1b6f2..c1b58a1 100755 --- a/contrib/history.py +++ b/contrib/history.py @@ -67,12 +67,11 @@ def main(): client.request('blockchain.scripthash.get_balance', script_hash) for script_hash in script_hashes ) - balances = [balance["confirmed"] for balance in balances] - total = sum(balances) for balance, addr in sorted(zip(balances, args.address)): - if balance: - log.debug('{:15,.5f} mBTC at {}', balance / 1e5, addr) - log.debug('{:15,.5f} mBTC (total)', total / 1e5) + if balance["confirmed"]: + log.info("{}: confirmed {:,.5f} mBTC", addr, balance["confirmed"] / 1e5) + if balance["unconfirmed"]: + log.info("{}: unconfirmed {:,.5f} mBTC", addr, balance["unconfirmed"] / 1e5) histories = conn.call( client.request('blockchain.scripthash.get_history', script_hash) From 844723f5f41700feede1069f7e573b0921117472 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 15 Sep 2021 13:11:16 +0300 Subject: [PATCH 105/113] Simplify 'blockchain.scripthash.get_balance' implementation --- src/electrum.rs | 9 ++--- src/status.rs | 103 +++++++++++++++++++++--------------------------- src/tracker.rs | 16 ++------ 3 files changed, 51 insertions(+), 77 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index dd9af35..3da7bc4 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -226,19 +226,16 @@ impl Rpc { (scripthash,): (ScriptHash,), ) -> Result { let balance = match client.scripthashes.get(&scripthash) { - Some(status) => self.tracker.get_balance(status, &self.cache), + Some(status) => self.tracker.get_balance(&status), None => { warn!( "blockchain.scripthash.get_balance called for unsubscribed scripthash: {}", scripthash ); - self.tracker - .get_balance(&self.new_status(scripthash)?, &self.cache) + self.tracker.get_balance(&self.new_status(scripthash)?) } }; - Ok( - json!({"confirmed": balance.confirmed.as_sat(), "unconfirmed": balance.mempool_delta.as_sat()}), - ) + Ok(json!(balance)) } fn scripthash_get_history( diff --git a/src/status.rs b/src/status.rs index 1d48482..ba1edf2 100644 --- a/src/status.rs +++ b/src/status.rs @@ -41,14 +41,9 @@ impl TxEntry { } /// Relevant (scripthash-wise) funded outpoints - fn funding(&self) -> impl Iterator + '_ { + fn funding_outpoints(&self) -> impl Iterator + '_ { make_outpoints(&self.txid, &self.outputs) } - - /// Relevant (scripthash-wise) spent outpoints - fn spending(&self) -> impl Iterator + '_ { - self.spent.iter().copied() - } } // Confirmation height of a transaction or its mempool state: @@ -138,39 +133,38 @@ pub struct ScriptHashStatus { statushash: Option, // computed from history } -enum BalanceEntry { - Funded(OutPoint), - Spent(OutPoint), -} - /// Specific scripthash balance -#[derive(Default, Eq, PartialEq)] +#[derive(Default, Eq, PartialEq, Serialize)] pub(crate) struct Balance { - pub(crate) confirmed: Amount, - pub(crate) mempool_delta: SignedAmount, + #[serde(with = "bitcoin::util::amount::serde::as_sat")] + confirmed: Amount, + #[serde(with = "bitcoin::util::amount::serde::as_sat", rename = "unconfirmed")] + mempool_delta: SignedAmount, } #[derive(Default)] -struct Total { - funded: Amount, - spent: Amount, +struct Unspent { + map: HashMap, } -impl Total { +impl Unspent { fn balance(&self) -> Amount { - self.funded - self.spent + self.map.values().fold(Amount::default(), |acc, v| acc + *v) } - fn update( - &mut self, - entries: impl Iterator, - get_amount: impl Fn(OutPoint) -> Amount, - ) { - for entry in entries { - match entry { - BalanceEntry::Funded(outpoint) => self.funded += get_amount(outpoint), - BalanceEntry::Spent(outpoint) => self.spent += get_amount(outpoint), - } + fn insert(&mut self, entry: &TxEntry) { + for output in &entry.outputs { + let outpoint = OutPoint { + txid: entry.txid, + vout: output.index, + }; + self.map.insert(outpoint, output.value); + } + } + + fn remove(&mut self, entry: &TxEntry) { + for spent in &entry.spent { + self.map.remove(spent); } } } @@ -190,7 +184,7 @@ impl ScriptHashStatus { /// Iterate through confirmed TxEntries with their corresponding block heights. /// Skip entries from stale blocks. - fn confirmed_entries<'a>( + fn confirmed_height_entries<'a>( &'a self, chain: &'a Chain, ) -> impl Iterator + 'a { @@ -203,43 +197,36 @@ impl ScriptHashStatus { }) } + /// Iterate through confirmed TxEntries. + /// Skip entries from stale blocks. + fn confirmed_entries<'a>(&'a self, chain: &'a Chain) -> impl Iterator + 'a { + self.confirmed_height_entries(chain) + .flat_map(|(_height, entries)| entries) + } + /// Collect all funded and confirmed outpoints (as a set). fn confirmed_outpoints(&self, chain: &Chain) -> HashSet { self.confirmed_entries(chain) - .flat_map(|(_height, entries)| entries.iter().flat_map(TxEntry::funding)) + .flat_map(TxEntry::funding_outpoints) .collect() } - pub(crate) fn get_balance(&self, chain: &Chain, get_amount: F) -> Balance - where - F: Fn(OutPoint) -> Amount, - { - fn to_balance_entries<'a>( - entries: impl Iterator + 'a, - ) -> impl Iterator + 'a { - entries.flat_map(|e| { - let funded = e.funding().map(BalanceEntry::Funded); - let spent = e.spending().map(BalanceEntry::Spent); - funded.chain(spent) - }) - } + pub(crate) fn get_balance(&self, chain: &Chain) -> Balance { + let mut unspent = Unspent::default(); - let confirmed_entries = to_balance_entries( - self.confirmed_entries(chain) - .flat_map(|(_height, entries)| entries), - ); - let mempool_entries = to_balance_entries(self.mempool.iter()); + self.confirmed_entries(chain) + .for_each(|e| unspent.insert(e)); + self.confirmed_entries(chain) + .for_each(|e| unspent.remove(e)); + let confirmed_balance = unspent.balance(); - let mut total = Total::default(); - total.update(confirmed_entries, &get_amount); - let confirmed = total.balance(); - - total.update(mempool_entries, &get_amount); - let with_mempool = total.balance(); + self.mempool.iter().for_each(|e| unspent.insert(e)); + self.mempool.iter().for_each(|e| unspent.remove(e)); Balance { - confirmed, - mempool_delta: with_mempool.to_signed().unwrap() - confirmed.to_signed().unwrap(), + confirmed: confirmed_balance, + mempool_delta: unspent.balance().to_signed().unwrap() + - confirmed_balance.to_signed().unwrap(), } } @@ -251,7 +238,7 @@ impl ScriptHashStatus { /// Collect all confirmed history entries (in block order). fn get_confirmed_history(&self, chain: &Chain) -> Vec { - self.confirmed_entries(chain) + self.confirmed_height_entries(chain) .collect::>() .into_iter() .flat_map(|(height, entries)| { diff --git a/src/tracker.rs b/src/tracker.rs index 7bdaedd..f96fc0a 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -1,7 +1,6 @@ use anyhow::{Context, Result}; -use bitcoin::{BlockHash, OutPoint, Txid}; +use bitcoin::{BlockHash, Txid}; -use std::convert::TryFrom; use std::path::Path; use crate::{ @@ -76,17 +75,8 @@ impl Tracker { Ok(prev_statushash != status.statushash()) } - pub(crate) fn get_balance(&self, status: &ScriptHashStatus, cache: &Cache) -> Balance { - let get_amount_fn = |outpoint: OutPoint| { - cache - .get_tx(&outpoint.txid, |tx| { - let vout = usize::try_from(outpoint.vout).unwrap(); - bitcoin::Amount::from_sat(tx.output[vout].value) - }) - .expect("missing tx") - }; - - status.get_balance(self.chain(), get_amount_fn) + pub(crate) fn get_balance(&self, status: &ScriptHashStatus) -> Balance { + status.get_balance(self.chain()) } pub fn get_blockhash_by_txid(&self, txid: Txid) -> Option { From 1fa77aefa67cd6feb13e9d279e92bbb9f43d4525 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 16 Sep 2021 10:29:40 +0300 Subject: [PATCH 106/113] Fix RPC results sort in history.py Traceback (most recent call last): File "history.py", line 148, in main() File "history.py", line 70, in main for balance, addr in sorted(zip(balances, args.address)): TypeError: '<' not supported between instances of 'dict' and 'dict' --- contrib/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/history.py b/contrib/history.py index c1b58a1..149ab32 100755 --- a/contrib/history.py +++ b/contrib/history.py @@ -67,7 +67,7 @@ def main(): client.request('blockchain.scripthash.get_balance', script_hash) for script_hash in script_hashes ) - for balance, addr in sorted(zip(balances, args.address)): + for addr, balance in sorted(zip(args.address, balances), key=lambda v: v[0]): if balance["confirmed"]: log.info("{}: confirmed {:,.5f} mBTC", addr, balance["confirmed"] / 1e5) if balance["unconfirmed"]: From b916c2802bdee5b9c98b420d2a93dada3cdf055e Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 16 Sep 2021 10:50:29 +0300 Subject: [PATCH 107/113] Add a few comments to p2p.rs --- src/p2p.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/p2p.rs b/src/p2p.rs index 5bc6904..9c2fab6 100644 --- a/src/p2p.rs +++ b/src/p2p.rs @@ -25,6 +25,8 @@ pub(crate) struct Connection { } impl Connection { + /// Connect to a Bitcoin node via p2p protocol. + /// See https://en.bitcoin.it/wiki/Protocol_documentation for details. pub fn connect(network: Network, address: SocketAddr) -> Result { let stream = TcpStream::connect(address) .with_context(|| format!("{} p2p failed to connect: {:?}", network, address))?; @@ -78,6 +80,8 @@ impl Connection { } } + /// Request and process the specified blocks. + /// See https://en.bitcoin.it/wiki/Protocol_documentation#getblocks for details. pub(crate) fn for_blocks(&mut self, blockhashes: B, mut func: F) -> Result<()> where B: IntoIterator, @@ -94,6 +98,7 @@ impl Connection { debug!("loading {} blocks", blockhashes.len()); self.send(NetworkMessage::GetData(inv))?; + // receive, parse and process the blocks concurrently rayon::scope(|s| { let (tx, rx) = crossbeam_channel::bounded(10); s.spawn(|_| { @@ -120,6 +125,8 @@ impl Connection { }) } + /// Get new block headers (supporting reorgs). + /// https://en.bitcoin.it/wiki/Protocol_documentation#getheaders pub(crate) fn get_new_headers(&mut self, chain: &Chain) -> Result> { let msg = GetHeadersMessage::new(chain.locator(), BlockHash::default()); self.send(NetworkMessage::GetHeaders(msg))?; From e99906ff834af0749374380e6655a5529444aa53 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 15 Sep 2021 17:39:05 +0300 Subject: [PATCH 108/113] Refactor 'blockchain.scripthash.get_balance' Move common code into a helper struct. --- src/status.rs | 59 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/status.rs b/src/status.rs index ba1edf2..1777692 100644 --- a/src/status.rs +++ b/src/status.rs @@ -136,35 +136,61 @@ pub struct ScriptHashStatus { /// Specific scripthash balance #[derive(Default, Eq, PartialEq, Serialize)] pub(crate) struct Balance { - #[serde(with = "bitcoin::util::amount::serde::as_sat")] - confirmed: Amount, + #[serde(with = "bitcoin::util::amount::serde::as_sat", rename = "confirmed")] + confirmed_balance: Amount, #[serde(with = "bitcoin::util::amount::serde::as_sat", rename = "unconfirmed")] mempool_delta: SignedAmount, } #[derive(Default)] struct Unspent { - map: HashMap, + // mapping an outpoint to its value & confirmation height + outpoints: HashMap, + confirmed_balance: Amount, + mempool_delta: SignedAmount, } impl Unspent { - fn balance(&self) -> Amount { - self.map.values().fold(Amount::default(), |acc, v| acc + *v) + fn build(status: &ScriptHashStatus, chain: &Chain) -> Self { + let mut unspent = Unspent::default(); + + status + .confirmed_height_entries(chain) + .for_each(|(height, entries)| entries.iter().for_each(|e| unspent.insert(e, height))); + status + .confirmed_entries(chain) + .for_each(|e| unspent.remove(e)); + + unspent.confirmed_balance = unspent.balance(); + + status.mempool.iter().for_each(|e| unspent.insert(e, 0)); // mempool height = 0 + status.mempool.iter().for_each(|e| unspent.remove(e)); + + unspent.mempool_delta = + unspent.balance().to_signed().unwrap() - unspent.confirmed_balance.to_signed().unwrap(); + + unspent } - fn insert(&mut self, entry: &TxEntry) { + fn balance(&self) -> Amount { + self.outpoints + .values() + .fold(Amount::default(), |acc, v| acc + v.0) + } + + fn insert(&mut self, entry: &TxEntry, height: usize) { for output in &entry.outputs { let outpoint = OutPoint { txid: entry.txid, vout: output.index, }; - self.map.insert(outpoint, output.value); + self.outpoints.insert(outpoint, (output.value, height)); } } fn remove(&mut self, entry: &TxEntry) { for spent in &entry.spent { - self.map.remove(spent); + self.outpoints.remove(spent); } } } @@ -212,21 +238,10 @@ impl ScriptHashStatus { } pub(crate) fn get_balance(&self, chain: &Chain) -> Balance { - let mut unspent = Unspent::default(); - - self.confirmed_entries(chain) - .for_each(|e| unspent.insert(e)); - self.confirmed_entries(chain) - .for_each(|e| unspent.remove(e)); - let confirmed_balance = unspent.balance(); - - self.mempool.iter().for_each(|e| unspent.insert(e)); - self.mempool.iter().for_each(|e| unspent.remove(e)); - + let unspent = Unspent::build(self, chain); Balance { - confirmed: confirmed_balance, - mempool_delta: unspent.balance().to_signed().unwrap() - - confirmed_balance.to_signed().unwrap(), + confirmed_balance: unspent.confirmed_balance, + mempool_delta: unspent.mempool_delta, } } From 0a04888673ba55347c35578ca6bc2a9d935b0f03 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 15 Sep 2021 21:00:17 +0300 Subject: [PATCH 109/113] Implement 'blockchain.scripthash.listunspent' RPC Following #475 --- contrib/history.py | 18 +++++++++++++----- src/electrum.rs | 21 +++++++++++++++++++++ src/status.rs | 27 +++++++++++++++++++++++++++ src/tracker.rs | 6 +++++- 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/contrib/history.py b/contrib/history.py index 149ab32..ce1142e 100755 --- a/contrib/history.py +++ b/contrib/history.py @@ -67,11 +67,19 @@ def main(): client.request('blockchain.scripthash.get_balance', script_hash) for script_hash in script_hashes ) - for addr, balance in sorted(zip(args.address, balances), key=lambda v: v[0]): - if balance["confirmed"]: - log.info("{}: confirmed {:,.5f} mBTC", addr, balance["confirmed"] / 1e5) - if balance["unconfirmed"]: - log.info("{}: unconfirmed {:,.5f} mBTC", addr, balance["unconfirmed"] / 1e5) + + unspents = conn.call( + client.request('blockchain.scripthash.listunspent', script_hash) + for script_hash in script_hashes + ) + for addr, balance, unspent in sorted(zip(args.address, balances, unspents), key=lambda v: v[0]): + if unspent: + log.debug("{}: confirmed={:,.5f} mBTC, unconfirmed={:,.5f} mBTC", + addr, balance["confirmed"] / 1e5, balance["unconfirmed"] / 1e5) + for u in unspent: + log.debug("\t{}:{} = {:,.5f} mBTC {}", + u["tx_hash"], u["tx_pos"], u["value"] / 1e5, + f'@ {u["height"]}' if u["height"] else "") histories = conn.call( client.request('blockchain.scripthash.get_history', script_hash) diff --git a/src/electrum.rs b/src/electrum.rs index 3da7bc4..b9684ee 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -256,6 +256,24 @@ impl Rpc { Ok(json!(history_entries)) } + fn scripthash_list_unspent( + &self, + client: &Client, + (scripthash,): (ScriptHash,), + ) -> Result { + let unspent_entries = match client.scripthashes.get(&scripthash) { + Some(status) => self.tracker.get_unspent(status), + None => { + warn!( + "blockchain.scripthash.listunspent called for unsubscribed scripthash: {}", + scripthash + ); + self.tracker.get_unspent(&self.new_status(scripthash)?) + } + }; + Ok(json!(unspent_entries)) + } + fn scripthash_subscribe( &self, client: &mut Client, @@ -406,6 +424,7 @@ impl Rpc { Call::RelayFee => self.relayfee(), Call::ScriptHashGetBalance(args) => self.scripthash_get_balance(client, args), Call::ScriptHashGetHistory(args) => self.scripthash_get_history(client, args), + Call::ScriptHashListUnspent(args) => self.scripthash_list_unspent(client, args), Call::ScriptHashSubscribe(args) => self.scripthash_subscribe(client, args), Call::TransactionBroadcast(args) => self.transaction_broadcast(args), Call::TransactionGet(args) => self.transaction_get(args), @@ -445,6 +464,7 @@ enum Call { RelayFee, ScriptHashGetBalance((ScriptHash,)), ScriptHashGetHistory((ScriptHash,)), + ScriptHashListUnspent((ScriptHash,)), ScriptHashSubscribe((ScriptHash,)), TransactionGet(TxGetArgs), TransactionGetMerkle((Txid, usize)), @@ -461,6 +481,7 @@ impl Call { "blockchain.relayfee" => Call::RelayFee, "blockchain.scripthash.get_balance" => Call::ScriptHashGetBalance(convert(params)?), "blockchain.scripthash.get_history" => Call::ScriptHashGetHistory(convert(params)?), + "blockchain.scripthash.listunspent" => Call::ScriptHashListUnspent(convert(params)?), "blockchain.scripthash.subscribe" => Call::ScriptHashSubscribe(convert(params)?), "blockchain.transaction.broadcast" => Call::TransactionBroadcast(convert(params)?), "blockchain.transaction.get" => Call::TransactionGet(convert(params)?), diff --git a/src/status.rs b/src/status.rs index 1777692..d282c24 100644 --- a/src/status.rs +++ b/src/status.rs @@ -142,6 +142,17 @@ pub(crate) struct Balance { mempool_delta: SignedAmount, } +// A single unspent transaction output entry: +// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent +#[derive(Serialize)] +pub(crate) struct UnspentEntry { + height: usize, // 0 = mempool entry + tx_hash: Txid, + tx_pos: u32, + #[serde(with = "bitcoin::util::amount::serde::as_sat")] + value: Amount, +} + #[derive(Default)] struct Unspent { // mapping an outpoint to its value & confirmation height @@ -172,6 +183,18 @@ impl Unspent { unspent } + fn into_entries(self) -> Vec { + self.outpoints + .into_iter() + .map(|(outpoint, (value, height))| UnspentEntry { + height, + tx_hash: outpoint.txid, + tx_pos: outpoint.vout, + value, + }) + .collect() + } + fn balance(&self) -> Amount { self.outpoints .values() @@ -237,6 +260,10 @@ impl ScriptHashStatus { .collect() } + pub(crate) fn get_unspent(&self, chain: &Chain) -> Vec { + Unspent::build(self, chain).into_entries() + } + pub(crate) fn get_balance(&self, chain: &Chain) -> Balance { let unspent = Unspent::build(self, chain); Balance { diff --git a/src/tracker.rs b/src/tracker.rs index f96fc0a..f40e581 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -12,7 +12,7 @@ use crate::{ index::Index, mempool::{Histogram, Mempool}, metrics::Metrics, - status::{Balance, HistoryEntry, ScriptHashStatus}, + status::{Balance, HistoryEntry, ScriptHashStatus, UnspentEntry}, }; /// Electrum protocol subscriptions' tracker @@ -55,6 +55,10 @@ impl Tracker { status.get_history(self.index.chain(), &self.mempool) } + pub(crate) fn get_unspent(&self, status: &ScriptHashStatus) -> Vec { + status.get_unspent(self.index.chain()) + } + pub fn sync(&mut self, daemon: &Daemon) -> Result<()> { self.index.sync(daemon, self.index_batch_size)?; if !self.ignore_mempool { From 577f54195b8c21f63fc4abec3c9364ddd1a73692 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Mon, 13 Sep 2021 11:50:21 +0200 Subject: [PATCH 110/113] Auto reindex when old database is detected This implements automatic reindexing of old database, turned on by default. When an old database is detected and `auto_reindex` is turned on the database will be destroyed so that `electrs` can re-sync. The user can still turn this off to check db format. The help message says "or inconsistent" - this is not implemented but we could in the future and it seems reasonable to explicitly state that this option will control that thing as well. Note: 0.8.x versions of `electrs` didn't contain format information - so we check if the database is empty and assuming it's legacy if it's not empty but without format information. The code is also restructured a bit and a few tests are added. --- Cargo.lock | 76 ++++++++++++++- Cargo.toml | 3 + internal/config_specification.toml | 5 + src/config.rs | 2 + src/db.rs | 144 ++++++++++++++++++++++++----- src/tracker.rs | 4 +- 6 files changed, 205 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9438192..8fd8723 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,6 +348,7 @@ dependencies = [ "serde_derive", "serde_json", "signal-hook", + "tempfile", "tiny_http", ] @@ -671,6 +672,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro2" version = "1.0.29" @@ -740,9 +747,9 @@ checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ "autocfg 0.1.7", "libc", - "rand_chacha", + "rand_chacha 0.1.1", "rand_core 0.4.2", - "rand_hc", + "rand_hc 0.1.0", "rand_isaac", "rand_jitter", "rand_os", @@ -751,6 +758,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", + "rand_hc 0.3.0", +] + [[package]] name = "rand_chacha" version = "0.1.1" @@ -761,6 +780,16 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -776,6 +805,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "rand_hc" version = "0.1.0" @@ -785,6 +823,15 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.3", +] + [[package]] name = "rand_isaac" version = "0.1.1" @@ -908,6 +955,15 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rocksdb" version = "0.15.0" @@ -947,7 +1003,7 @@ version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" dependencies = [ - "rand", + "rand 0.6.5", "secp256k1-sys", "serde", ] @@ -1034,6 +1090,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand 0.8.4", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.2" diff --git a/Cargo.toml b/Cargo.toml index 96a7561..a468488 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,3 +52,6 @@ features = ["zstd", "snappy"] [build-dependencies] configure_me_codegen = "0.4" + +[dev-dependencies] +tempfile = "3.2" diff --git a/internal/config_specification.toml b/internal/config_specification.toml index 7b937e9..9738e1f 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -17,6 +17,11 @@ count = true name = "timestamp" doc = "Prepend log lines with a timestamp" +[[switch]] +name = "auto_reindex" +doc = "Automatically reindex the database if it's inconsistent or in old format" +default = true + [[param]] name = "db_dir" type = "std::path::PathBuf" diff --git a/src/config.rs b/src/config.rs index c92572f..46dd31f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -132,6 +132,7 @@ pub struct Config { pub wait_duration: Duration, pub index_batch_size: usize, pub index_lookup_limit: Option, + pub auto_reindex: bool, pub ignore_mempool: bool, pub sync_once: bool, pub server_banner: String, @@ -294,6 +295,7 @@ impl Config { wait_duration: Duration::from_secs(config.wait_duration_secs), index_batch_size: config.index_batch_size, index_lookup_limit, + auto_reindex: config.auto_reindex, ignore_mempool: config.ignore_mempool, sync_once: config.sync_once, server_banner: config.server_banner, diff --git a/src/db.rs b/src/db.rs index 386dbac..03ded1e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -31,9 +31,7 @@ struct Options { /// RocksDB wrapper for index storage pub struct DBStore { db: rocksdb::DB, - path: PathBuf, bulk_import: AtomicBool, - cfs: Vec<&'static str>, } const CONFIG_CF: &str = "config"; @@ -42,6 +40,8 @@ const TXID_CF: &str = "txid"; const FUNDING_CF: &str = "funding"; const SPENDING_CF: &str = "spending"; +const COLUMN_FAMILIES: &[&str] = &[CONFIG_CF, HEADERS_CF, TXID_CF, FUNDING_CF, SPENDING_CF]; + const CONFIG_KEY: &str = "C"; const TIP_KEY: &[u8] = b"T"; @@ -53,6 +53,15 @@ struct Config { const CURRENT_FORMAT: u64 = 0; +impl Default for Config { + fn default() -> Self { + Config { + compacted: false, + format: CURRENT_FORMAT, + } + } +} + fn default_opts() -> rocksdb::Options { let mut opts = rocksdb::Options::default(); opts.set_keep_log_file_num(10); @@ -68,19 +77,20 @@ fn default_opts() -> rocksdb::Options { } impl DBStore { - /// Opens a new RocksDB at the specified location. - pub fn open(path: &Path) -> Result { - let cfs = vec![CONFIG_CF, HEADERS_CF, TXID_CF, FUNDING_CF, SPENDING_CF]; - let cf_descriptors: Vec = cfs + fn create_cf_descriptors() -> Vec { + COLUMN_FAMILIES .iter() .map(|&name| rocksdb::ColumnFamilyDescriptor::new(name, default_opts())) - .collect(); + .collect() + } + fn open_internal(path: &Path) -> Result { let mut db_opts = default_opts(); db_opts.create_if_missing(true); db_opts.create_missing_column_families(true); - let db = rocksdb::DB::open_cf_descriptors(&db_opts, path, cf_descriptors) - .with_context(|| format!("failed to open DB: {:?}", path))?; + + let db = rocksdb::DB::open_cf_descriptors(&db_opts, path, Self::create_cf_descriptors()) + .with_context(|| format!("failed to open DB: {}", path.display()))?; let live_files = db.live_files()?; info!( "{:?}: {} SST files, {} GB, {} Grows", @@ -91,15 +101,55 @@ impl DBStore { ); let store = DBStore { db, - path: path.to_path_buf(), - cfs, bulk_import: AtomicBool::new(true), }; + Ok(store) + } + fn is_legacy_format(&self) -> bool { + // In legacy DB format, all data was stored in a single (default) column family. + self.db + .iterator(rocksdb::IteratorMode::Start) + .next() + .is_some() + } + + /// Opens a new RocksDB at the specified location. + pub fn open(path: &Path, auto_reindex: bool) -> Result { + let mut store = Self::open_internal(path)?; let config = store.get_config(); debug!("DB {:?}", config); - if config.format != CURRENT_FORMAT { - bail!("unsupported DB format {}, re-index required", config.format); + let mut config = config.unwrap_or_default(); // use default config when DB is empty + + let reindex_cause = if store.is_legacy_format() { + Some("legacy format".to_owned()) + } else if config.format != CURRENT_FORMAT { + Some(format!( + "unsupported format {} != {}", + config.format, CURRENT_FORMAT + )) + } else { + None + }; + if let Some(cause) = reindex_cause { + if !auto_reindex { + bail!("re-index required due to {}", cause); + } + warn!( + "Database needs to be re-indexed due to {}, going to delete {}", + cause, + path.display() + ); + // close DB before deletion + drop(store); + rocksdb::DB::destroy(&default_opts(), &path).with_context(|| { + format!( + "re-index required but the old database ({}) can not be deleted", + path.display() + ) + })?; + store = Self::open_internal(path)?; + config = Config::default(); // re-init config after dropping DB } if config.compacted { store.start_compactions(); @@ -190,13 +240,13 @@ impl DBStore { } pub(crate) fn flush(&self) { - let mut config = self.get_config(); - for name in &self.cfs { + let mut config = self.get_config().unwrap_or_default(); + for name in COLUMN_FAMILIES { let cf = self.db.cf_handle(name).expect("missing CF"); self.db.flush_cf(cf).expect("CF flush failed"); } if !config.compacted { - for name in &self.cfs { + for name in COLUMN_FAMILIES { info!("starting {} compaction", name); let cf = self.db.cf_handle(name).expect("missing CF"); self.db.compact_range_cf(cf, None::<&[u8]>, None::<&[u8]>); @@ -220,7 +270,7 @@ impl DBStore { fn start_compactions(&self) { self.bulk_import.store(false, Ordering::Relaxed); - for name in &self.cfs { + for name in COLUMN_FAMILIES { let cf = self.db.cf_handle(name).expect("missing CF"); self.db .set_options_cf(cf, &[("disable_auto_compactions", "false")]) @@ -239,15 +289,11 @@ impl DBStore { .expect("DB::put failed"); } - fn get_config(&self) -> Config { + fn get_config(&self) -> Option { self.db .get_cf(self.config_cf(), CONFIG_KEY) .expect("DB::get failed") .map(|value| serde_json::from_slice(&value).expect("failed to deserialize Config")) - .unwrap_or_else(|| Config { - compacted: false, - format: CURRENT_FORMAT, - }) } } @@ -275,6 +321,58 @@ impl<'a> Iterator for ScanIterator<'a> { impl Drop for DBStore { fn drop(&mut self) { - info!("closing DB at {:?}", self.path); + info!("closing DB at {}", self.db.path().display()); + } +} + +#[cfg(test)] +mod tests { + use super::{DBStore, CURRENT_FORMAT}; + + #[test] + fn test_reindex_new_format() { + let dir = tempfile::tempdir().unwrap(); + { + let store = DBStore::open(dir.path(), false).unwrap(); + let mut config = store.get_config().unwrap(); + config.format += 1; + store.set_config(config); + }; + assert_eq!( + DBStore::open(dir.path(), false).err().unwrap().to_string(), + format!( + "re-index required due to unsupported format {} != {}", + CURRENT_FORMAT + 1, + CURRENT_FORMAT + ) + ); + { + let store = DBStore::open(dir.path(), true).unwrap(); + store.flush(); + let config = store.get_config().unwrap(); + assert_eq!(config.format, CURRENT_FORMAT); + assert_eq!(store.is_legacy_format(), false); + } + } + + #[test] + fn test_reindex_legacy_format() { + let dir = tempfile::tempdir().unwrap(); + { + let mut db_opts = rocksdb::Options::default(); + db_opts.create_if_missing(true); + let db = rocksdb::DB::open(&db_opts, dir.path()).unwrap(); + db.put(b"F", b"").unwrap(); // insert legacy DB compaction marker (in 'default' column family) + }; + assert_eq!( + DBStore::open(dir.path(), false).err().unwrap().to_string(), + format!("re-index required due to legacy format",) + ); + { + let store = DBStore::open(dir.path(), true).unwrap(); + store.flush(); + let config = store.get_config().unwrap(); + assert_eq!(config.format, CURRENT_FORMAT); + } } } diff --git a/src/tracker.rs b/src/tracker.rs index f40e581..f671c5b 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -1,8 +1,6 @@ use anyhow::{Context, Result}; use bitcoin::{BlockHash, Txid}; -use std::path::Path; - use crate::{ cache::Cache, chain::Chain, @@ -27,7 +25,7 @@ pub struct Tracker { impl Tracker { pub fn new(config: &Config) -> Result { let metrics = Metrics::new(config.monitoring_addr)?; - let store = DBStore::open(Path::new(&config.db_path))?; + let store = DBStore::open(&config.db_path, config.auto_reindex)?; let chain = Chain::new(config.network); Ok(Self { index: Index::load(store, chain, &metrics, config.index_lookup_limit) From ce96481522ff4cd7d1a88fc553de14b4cc286f85 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Thu, 16 Sep 2021 11:26:47 +0200 Subject: [PATCH 111/113] Improve documentation of 0.8.x -> 0.9.0 upgrade This should hopefully make the major changes more visible and avoid surprises. --- RELEASE-NOTES.md | 15 ++++++++++++++- doc/usage.md | 35 ++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 445b2e5..e12135c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,9 +1,22 @@ # 0.9.0 (TBD) +**IMPORTANT: This release contains major changes, please read carefully!** + +The two main things to watch out for: + +* Database schema changed - this will cause **reindex after upgrade**. +* We now use **bitcoin p2p protocol** to fetch blocks - some configurations may not work. + +See [upgrading](doc/usage.md#upgrading) section of our docs to learn more. + +Full list of changes: + * Fix incorrect ordering of same-block transactions (#297) * Change DB index format and use Zstd compression (instead of Snappy) +* The database will be reindexed automatically when it encounters old version * Don't use bitcoind JSON RPC for fetching blocks (#373) -* Use p2p for block fetching (instead of reading `blk*.dat` files) +* Use p2p for block fetching only. + This is safer than reading `blk*dat` files and faster than Json RPC. * Support Electrum JSON RPC batching and errors * Use `rust-bitcoincore-rpc` crate * Increase default `index_lookup_limit` to 200 diff --git a/doc/usage.md b/doc/usage.md index b1364ed..36dc76e 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -487,16 +487,33 @@ $ ./contrib/history.sh 144STc7gcb9XCp6t4hvrcUEKg9KemivsCR ## Upgrading -> Note that in 0.9.0 we have changed the RocksDB index format, so you may get an error when reading an older DB. -> In this case, consider moving the old DB directory and perform a re-sync of the blockchain. +### Important changes from versions older than 0.9.0 -> **If you're upgrading from version 0.8.7 to a higher version and used `cookie` option you should change your configuration!** -> The `cookie` option was deprecated and **will be removed eventually**! -> If you had actual cookie (from `~/bitcoin/.cookie` file) specified in `cookie` option, this was wrong as it wouldn't get updated when needed. -> It's strongly recommended to use proper cookie authentication using `cookie_file`. -> If you really have to use fixed username and password, explicitly specified in `bitcoind` config, use `auth` option instead. -> Users of `btc-rpc-proxy` using `public:public` need to use `auth` too. -> You can read [a detailed explanation of cookie deprecation with motivation explained](cookie_deprecation.md). +In 0.9.0 we have changed the RocksDB index format to optimize electrs performance. +We also use Bitcoin P2P protocol instead of reading blocks from disk or JSON RPC. + +Upgrading checklist: + +* Make sure you upgrade at time when you don't need to use electrs for a while. + Because of reindex electrs will be unable to serve your requests for a few hours. + (The exact time depends on your hardware.) + If you wish to check the database without reindexing run electrs with `--no-auto-reindex`. +* Make sure to allow accesses to bitcoind from local address, ideally whitelist it using `whitelist=download@127.0.0.1` bitcoind option. + Either don't use `maxconnections` bitcoind option or set it to 12 or more. +* If you use non-default P2P port for bitcoind adjust `electrs` configuration. +* If you still didn't migrate `cookie` electrs option you have to now - see below. + +### Important changes from version older than 0.8.8 + +**If you're upgrading from version 0.8.7 to a higher version and used `cookie` option you should change your configuration!** +The `cookie` option was deprecated and **will be removed eventually**! +If you had actual cookie (from `~/bitcoin/.cookie` file) specified in `cookie` option, this was wrong as it wouldn't get updated when needed. +It's strongly recommended to use proper cookie authentication using `cookie_file`. +If you really have to use fixed username and password, explicitly specified in `bitcoind` config, use `auth` option instead. +Users of `btc-rpc-proxy` using `public:public` need to use `auth` too. +You can read [a detailed explanation of cookie deprecation with motivation explained](cookie_deprecation.md). + +### General upgrading guide As with any other application, you need to remember how you installed `electrs` to upgrade it. If you don't then here's a little help: run `which electrs` and compare the output From c11ee11395e56861fea58456c98dc03ba9af6468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Habov=C5=A1tiak?= Date: Fri, 17 Sep 2021 13:29:32 +0200 Subject: [PATCH 112/113] JSON spelling Co-authored-by: Roman Zeyde --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e12135c..9209bda 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -16,7 +16,7 @@ Full list of changes: * The database will be reindexed automatically when it encounters old version * Don't use bitcoind JSON RPC for fetching blocks (#373) * Use p2p for block fetching only. - This is safer than reading `blk*dat` files and faster than Json RPC. + This is safer than reading `blk*dat` files and faster than JSON RPC. * Support Electrum JSON RPC batching and errors * Use `rust-bitcoincore-rpc` crate * Increase default `index_lookup_limit` to 200 From c1ce81112fb36de359d24ed70403716e9ffea72a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Habov=C5=A1tiak?= Date: Fri, 17 Sep 2021 13:30:09 +0200 Subject: [PATCH 113/113] Reference auto reindex PR in release notes Co-authored-by: Roman Zeyde --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9209bda..2b73f02 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -13,7 +13,7 @@ Full list of changes: * Fix incorrect ordering of same-block transactions (#297) * Change DB index format and use Zstd compression (instead of Snappy) -* The database will be reindexed automatically when it encounters old version +* The database will be reindexed automatically when it encounters old version (#477) * Don't use bitcoind JSON RPC for fetching blocks (#373) * Use p2p for block fetching only. This is safer than reading `blk*dat` files and faster than JSON RPC.

PbB;P)Z%2oicp|5Z z<$K_@V+&h<-Pco9()R%3I&22&7UW_3hXqnwQEPkruy`# z3Io>ny*Q7aXw#i84JMN4;D%>*FhGQN>iY)b>8U6*`8Pa~sB`|G=r-Wj{Lz6pB%iu{ zp2Sy{*9#SHn#JxJ1nlO%KQchiQ3Si5Ij{jq&Ui8&q`T=b30i(}FDb_iX*?trZKrUH zR7M$?g)Q`^(*OgpLJ`camN=piUc_fH%(geIHoL5O2G;t1i+bi=gT?ij(};p?F3cMC zaNY{Xlvq^re0f3p6`sLu{$~wokbX$U=3~t4-@$4C51ABFDCRa!j`njC*JQ)+RWvaV z4NT&p+(`LR${{ka03C-`KoM0E5@` z<26yDYyJC*Li#A(^M!Zm-hsFARMI@A<@jbKRHxfZeKh9hu|fB6wAT>oQ;amha-f#}g6 zBYq$I&1B_vLxKVu(8*lo&krfe!G(d-F~EG&p9l>0_yl8IWtmA0F0oQ5^Gh9lIUZ`8 zz5OTJyrAXk_iN3h4ULoOR~p`-(u6R*q{&l;!VZ>kVm8@90l((2(}Vj>{Mw$+Ia#6n zl3X{~50->NkDYpJA%|Ge`+X2e&JU=aP*;3ONc4;ta@rr;YxOuBFEX_Q<;`wK6Q+c9 zqMC6jU?G*uU2>&+X%xhO@*<}}$BRm&JIorcls2W4w**25iPzwfp$@XB@uy|QTQYpQ zZVQ8-iz3(Vcf~0OyP&?yiOu1ckx!VWykTHlhUM$=U%uyV=}fv6JJHaa5ZWg4fuNDQ ztC-jY5{3pVBlFoaL)RlK7W#8c^Q$)TXRf#1IDK-c*zJT?ul|uq>iv7iijYo~&w>(! zetmVTibLM_BM+ZZ7!w`3IIi$pz)XMI@QY1F^*V)8>b|a4TG)7A5*;J#kV$QPz6kn1BKb~Ly%_1I-l%Lg zscpVc#rSZoX20+(wfdIwBA#HvdHg>l0o-dJ;Eu3V4u!8TO!HnIJF~TaD-D(>E)5(H zfq>a7ZhC^zIyeGhds^f28FMZM3y_M#xL~@siHhTraJT=kwB8H;9vctykBLa z-@4$^KdfXD=NKZ-6*{{0G6VHFDgV3vW@~m~h!CvKEbe!^3GS4U6!TTJYq*wUgUua~e;eBwPbt>WFU- zWwNeu{$9ZkgIx~VyPOA((=|uszU&~1|E@N5Daj8X9(GP}TX)2X>s_SSjoJ={6mR%5T`Bhgv z`&?e&ol_NU+~5?smj`D5BLhZBhIjqYnRHAq3eTV&HO;*4;V!OLm;z5AX9|~2j<12% z()gWwE6($oWx~1sUieBAW&4&|NCYKn`=qY|Es>QZfTZ!^W|1XgaSdqOby`2gxbv0X zj9p32l&R8fl$Q`mID?VMwnKMO*y4$2qTkW`dKb~BFm`u}s9FtH$?Xsq!lYD!k^Z*e zrBj@g)=!LCy3LHqULl>N%Gp{g;{jrI)HjD3%h(mby2BC_lR2scIc_N8rqMgVd3(d# zi%=|VEP(z=eeIK=K5**=$W=l0SqeLpBbW2yFfI}nzO}Z^M3*JHN#ng!)~`OfI?!#XKvqUh$4dsr#3$M$XC91m zjRdLqaQc1{k#P_TQ|I#>hR!VN6ARPIWJ+02+%edCHQsJ6AYz#)yd6@AhBf(4euT7x zkO7F(c%oJl)ew4kq`a3l;Pp0J?hgq_TOP{VHw2ED zwH(qOogO`W&N+$v29k(5pC03DXajWc?g*iSf^PCQSflQX>+Rc;Evf}aD-a`RX32gKF=oTu^gABeuD@fQ!g8uA`qg0Ru;F<0(6}o& z?i~~E`>b%>k@rCT!U2>yS<5Z!iGtTV!mrQ0Y84nmgFl0j_F-WK!h3WXuaNipMaKek z^lNIJtW|Wqgg;#T7EO!k{SOI4XFHsqvG_;Ay~(-iCOdPzKwsUEm|HF;tvaODk0_wu zguksP&uMGbk{BZUjB2W*g#YTqiy39;+8#RTn@YPK_&gv>-k|1+e!%S0$O~ z8F;Xo*Z+Qc-)7u7qU4n1S8RXwTcpAFo@bf$U&%@r_9BHny)YUaRe*Rj#t+qo2N>Hr z3vW|ZHU|+j7P++j9uE(7{)>zXg;#k#1TJ6n~7XhPM9A zOQRv1zX6aS-J^v!WHxtne9~c7EMAY|3Gy$D!I<6)A))qq&uYnokvv@(59aCDClgw- zR()?P=hJ&+j_!{R=*}&+S(MAShyO!@N}*9g(~+u^*6&7sb$sV08@TVujYa?B4lPyg zdh@CVNDugNYlCyt{~5l;Vl~r22Q+u;vvIfCeDGYw9lL*I1-$2amIK`u!Z7*LRxpJk zdGxK5rKz;Wy0(kc%5Uq%(Si2Ib^2<+h-I=g$;(0uv1_yCnfV;vCAvvGEqWe%uFX|9 zhR0B~zkPYgk#1JM7B7uj?F|bNTUMW)^MTOd3sE7PjiI+T6RUPWegF+AA#?CeG+#|N z*;i(*OUk2gpX;gGqL^j)B#E`K?|_@qj$s`0mm}t{mAZB{Cu;8cs>K62#yUdfXU1O} z!seBN-2_q*Ndmj#vEP*2N-K7tjPlH>AWlN`twSLVzQ6tdwJA|iLO6+!j4JE80_Fco zZ3E9X?8%jOP^)o=qGaT%ubvSW_dJJd!(;qmMY!k?d zb=^>|wQtRkAahdP%eT!d_e>_-*O#FrCj|lFo2CBGgT96f%~+!LX%<~7pnkMU$~rcA zn=~|VDqv2e3OHTmZO{+pcbcqYtA=4IJPPmN4o^K#N?^0s73uY6+vp#Ug+d3N5V%|* zQ42U%#h4f!dX^QkC!$kGX19m!Mcc|?B>GcJKO=2FyYm}&6iZWekxdG zHBC?$h!yrg%&h*F0M|8%zQgiNKZHFz8(Xa=L}kpt@%A#&n>oV^d2kAF0uDn?Q|rvu zMNp^lV)M7RGTYt3lDkY*f%hSLY;O$%!9I_2QJzBa9bDQD=ho~t3_wgbzF1*n!!SuV zJ-XB9V}Cpr`m2L6TU5S5+=zznjKY!qjuez5G&Asslq})To9eY%!s?4&831mofm<^WwR8GHS63E)>CtRT`68!Mh$@@3 z!OZm5yEcd%%p{~oSdPI7*Ao$mU%y*Kc3yeeqgihD8tqh@#rJDsdluMDf0=?Fe4$@R zbS*r0Ay&tW5G35H#cZ>xuG~iqHZpA`eys{<6Q0@{NgRTvZjM?3blY$=Q-6Eq9}|3w*z zqFP%VwcC8kZ6fJ4cQq5x-*t`q|FwBMlpH?j`|)y)mJ8ZKdD<-qP7R#F4*WgNH`Pix z6g#zTZLF7Q*2rt1nbFuRgZFYbnx#Bb(^O&==$}}asbe!v$Y_O7&r{ZIZfRTR8QezD z7LDsdvslK)|A}87MB*yz42dLIfGSUqemo(o58Y+G`P%6Gej^}k;d?+GTp(cKwKEOp zfq1NYxUol(r6$EB#wf!^L?SU)Q2}PhGKR(D$iUhA41SVe<3U*s{i}m~>q-rYxYH~r z<;q>z?Sx|Fx&s*ePkW&%Qaq!d48j<~-Q*0u-z}qG8Ze6RMWm_TH7nAZ9RrfIAbxG? zePHPda>q{J>OXoGC1Ht%os~i@fK*vd9IYY&92A4djM`GZ^5eq%5LwzRW4qG{iPys9 zI~LD|ilC`5);X&MJ3W8qb++7g-MFv4a07({yv+RD5|UVh3#b2(n2qV# zJ(Er93z3FJl|hYluBnS?_H1!W#cw#Y734c%fh!&Q4MI4kHC@;ZUf;A5PnXXa3?_pX z$Vn9=KC$e(C;&iYhDA?7RBzm${TGP3IqC1byeqHktAHfcL38UeZ`x2 z%uW0nT~-fwZe;E4&WpG1fl8(BV$K_M`fT2^3S;6K$R%n#yX0d8h0Us>YTiQml$y z-&%`_VzsHw9it?~wNX8>FqdWbhyEo+DN?usA3Gl!+>(lfo3C1ICQP3eG%hCJM__ZL z(tx;!klVsyorQhOJJJw1>`$yVTO|LDMM}>y!US9~3@IQrVu%zCFJ`#>j)gjA^4s0; z9`sl@2W*0}^g#uF2@BZ5gaA2K&`>>hXdsQ`8r@HI)Mb#bd|t@f36)XsjmnFsm43=~ zM_O&5-ZG3Ukq)bo4lkW%_qHE(_1vcX_4l#Juvxe`M;IyW!Ni#$|e8=B%Cc>$ABlU7~pIaGlRIrG=--JkHa&y`-07 ziRI=TYPD{gDtH2T94}qz2u?X=a;iJqKncwvK(P@P7>`hF^%rSJ0EN)gv|2?up}_#9si4ahr{@B9flKUzdX-0yljtH%O%@&DX1{c9@Q4+ zDIpS#1AfWh)>qmb!d9adDDn&Sbn9f>rh-Cq(q%`#J>|Q@tO;w#J8=-nTWhGn_A|=~ zY}L4F?NTM2E28l}Cm6lzv2DK*V_ts_z&^pZ_Bec0CLd?T=WGygt&R)qNs}Tli!F_N zX{gdw_I1G8EUj}k>U^m+_0tDeko|phNgv+L!1oZGm*tz%&D+9|I$)+h{U*ufrq=Jz z0`VaK-t^r-ooRX-lLCS^2Kl!-sRz*H^S~KD6i&eF90}SMake>93CMQI(Mu$F1#F^a zqw|t%zh)!w-od{hk&BL#fGV0Lmp;6~x@>lcHlE%w;8O8IIh69}@L9FYDeu z1*QWMM1DHVRp@E%w&^YLWa&k!jEaEmcjG|1P|-(jZ6jUEt62Vd2kEF%avzM%IZ^fn z4KC4g(!Aef|A=I{F<{-cpD*5 zU!PM=2V+jNbL@&c4(H9)F3Q!C?&OPA!#WI{@pH0W$7hAE(HTF>Z1i?_^y9i@W1xAE z79NVkD^_=I7y3sg(BbLhY0aZD9jwKYC|k}6sD(ds4r$TlTK}K|pGej`#)dyW(M=lk zI(rQYA!^r+#nynO^x}&N&XazVho2c~^S1(X5OtRj6zK<@^Q$EItWQ)vd!zalY!D+P zPb|EoLW;Zf#9!abdiG%bajJyxI@|;$ddH9*$!g^p=RVL!iO+oKf7a5#Kg6Xdt$cdQ z8J0>``J1-Pu|5G&rR_3}4;UFBBIv!mx7Wk`3rRaktT(pQUxvJ)Jw7?C*tdS*7V*6W zYqMUYev;W?|Czj!C5|SebQ0a@!9ygj7Uyc6%O=Ij=}{DYEAP+7F-KFA3RX@=$Nb+UHMvTv9&3&KL4tk#8;ZmbvG-%6 zp|^t6=QqZ|%1CKAUE$<~nx05gIbt9Z7cTnu+Iy06>0NLPtNUo7_Pw3*ODp%kvHV4l z(31UuHSs?oNe&el#e}F&oHLfn5lJp9NimA^2N^0;Ms-9Bp)R*sJQ?vW8Yp$SLrRHW z&*bajBXOHJ?r_x^^U_Q)uDnurKeU$wYTIp`_y{mIBy&i{Wm!Lxy;QhS>73U*; z%|yafB=C<+pjti+@Yi`%UPkRKYDcPz70cSk=I2Pav`WJuOh9>0&MY1K>Rvo!nMHgp zVWj0aT9tCZO)mOGn}feG-;F!%1{gTuNWRC-)@!E>$2x1gw|LWK^X+vr%;Ti=Z|f_6 zK|ZsW<~a4flU$K^6XJDbqoJEqRxp1jJ5m$p5j+t6<^Ds5hsEA+m1u0;DxTiKCAoR3 z|9rW)md11#;s?EP5YYM@H#8dzC~BjBAQlUZAw?AF+9&kN%yW`DioA@up$d4MwV1KR zjT^#n?}@$NU9WscL#wfR8)A#{t;30~{af0gLonFue}rJQNf#~ZClu4?gOwhTEE8GJ ztC633Y5LVRJVurp&iNNm8@3CZGTBN(Xk?!v9 zF6r)4Y3W9kZV;4`?v#*j*bnOW>i#$Uo?|%YTI=o=5;u|mIKrvanBdZ9fph86aN!Pk zV%DK)UDVd7wzekupL!YjGe9k6Y?PaIEwa@%ykN26hBNW)eswP{8a@B`G_x31gfO(H z{s^GRwC4jqn;Kr8wh*b?pAZ8kqRha*-%hqVBNvh98-VV3_g5x_@p3Zcfm_jXFm2sH zAEC13iI3m%3r^pOOcOntJFSuez1xb(T`7D0_!RaHRxV1LwLb3!STP{xix1Hn}nb2PY^M0Mh@4OH|Bl8WW-sI-}yw3?~+~ zoxkMLrmWU-rwmDkmsun5K8U#Uktqd}*fvQzH1(o1U64@!%L<_bo+mZuso5@bB(Svu ziRrtEADheF$XhDgIAYr2<(>_7-IG9G%@ZdleV$7*Qw)UvWI|S-*uD^EEJ%`?zJkoF z9FuAk5>fWVPfrl*$FPu|2F~erqRMoXu>^U`HMar?Zwo$AbcM*ISIId2Q-QP3!Ys;G7IMsyK<-&uBuBG- z1M~<6NhZ>VwcgRMy`P#Om#?Ecg!he^_74!>f5}`UmgP~JoTQWf%hX;KN|mWc_a61+ znUyCi+mzpPu7Rt?M#+#_|2$8%cA(k;mSy`?Wc!8A_btsj1(MyiW+R$$D6{afk4MWj zz56~-^}|4Mr);FN1aO>3vv2N#Ngg_*QnVph${IZu&(YJ}1HW!(aI63W<&REZ`IAXT zSDd;VRHC2>pAm3I<6cxP>*|W{8?ZmiJ-(R*P6}$|k{W6OV6ptKk6QgRL$a?^AgZYL zrRoqzCJ=^ffqNg2n9>wrLxe2k&0SHz^Tg#UYTYT1qTGJu6Kff4g)}j5b5Q-&DOwBG zMDRNo1Cup5;fv)^LkQauAYz1H;xR5`w6c^Rnx}%mr);MR zX`0;9*K>{*XXCAA+S3gLD5fL(_Op;!DSh2C`y%nX={0O2-8blZA~iWresZIk7pOAA z|BX5&GSmw1DeOj)vYkbf`N4(6CTT%ZS=JXp4RhwWSd&0(xq3g__2wERJpr=Y%>y5v zWd$)^DN*vBAbIF<&_{<+V37g|iNcAh9wl5Q@czek*UJTdRtm3_oS0euTvu8|y$p>| z`H=sRpdK>PrfTNRGQ4#Vb;;>u{ZJh_q&UNiQ$C1sugTBG0ev6#EXf0mr4v#&GWHJS zJ|(AbotU|vNHH!Bio{^*zMlpW$z9g;?#5I^&#*Tw?4nFFV(i1i*~3vtbVvP3ze?Vt z;_hE(`W!=(-o|#has8I^gkc~M9(wGgF}SkSQaLIQjPcA2h}#t1QnUY^R!>u(Gd?o3)!lByq|ry?ET#&p+FPEKq&93c7O4%C*2MArHt)|(f7d{W07 zi1UdjSgs`+$pwP$gkoji<_y$1ydf2r^iuO;$X| zY9im#MP3*m#&w^fsY#nqs0Rk3>E1hT8ezQaZ?~9%U#q0SDS*5BB1}SblK_9*@-xO) z$n>e4(x3UVHRp)Ghc4n`2;G-Wsl6A*NV$Zsl)ImnQ>h)x=Z*OmP}lk8_n#)@w~olz z^D_GB#0EYDoC7#XJlAnqyckLy=ZGLO(UzRrwKi~xA3F!CYso5V=$3{u8ehVt;#!+! zFQLQQmHcNqW__1CEOuuYpD0GIC7Bzj>A1cGeA1JXp}H`c@C|TVpwrxU1vdm{CZtTt znW0@8nqoWN?r7hr$gkM2h20}v52N%W+y3au zGfot=EB-4}#hXaX*64ZzyD;ZyuY4v=o#-rrIe~Ox1e}LQn#8_*pp2^zj&*L?_G+Lv zWQeQX>h|ghB2z!q=u!6h{KE#hnFWYU<2bW7Hnvu)=uB`E2uenR$GfJ%VSbb5Wd=Ui zIVVoPg42Ib;VAp(OZD!!vu;pBD;+hLA*73 zw88wmx;$w7G3S%Iew~_t2t{{=8KxN#!rLH)`Wf#nc6Lm!k6xrB(;xEAO+@o=wcM@LvBZFYNDT%aX>3 z_eI>6ku~docLb+64$Yo$ZzS;}h4$-grcrE@9z1@Kl;jJF(TBR0oR?woY>nmaCa5{p!f{YQustU!91@H_qGM z-(I0+Z&?hH{MBh>6RT1=w${G$@Gh;7;wlxgg}iH{)^qWlmy*%5`BWqbQudVwUn728 z_RbT;SlVWeb=ITBi6wgy??ely5&~c*xD7k?BMd;qX|SLhD6i!AlVjgJ2?5$)Wa{wUKE^O zIzuUH@@!bI*BsT4{@L<=dAU(C^i^C`GDh}5Rw`7S{yt*V!a8%odpO&~UV0x22oNE$ z3giYakS5=Jw}gVw`xA|AykE0Ka;;0y`ok-{n5QQ^P-If!^a>{A6K~7Js376Urd?>J zj)e8hk-AkS&@h`hyZ`w#=zhyYlpS{GWRpPKIKW6gsNocqi{5@EkOr0n)%nnBxw-QV zf|OzHavuiv1(sQWo{m}hqVdRaQzD9bL|vh&`p4cP;ip%4P>}X2p>DZ8sBuESmOu>m zZKSmGJIr1nTXKK5Xxj4XF84C}@0i;V7myzgTWQdf*S=FP4CNj-oXtByOCqwI>P3* zy2{56W*qDW4`K|`3G+Kg)CQh81C_)fXAcDNn&oxxpKEMWTY40zbRO$V2mZ88(l|h< z<zIy;Y~n^1_B2NS}hfFMka-qihG7=T@?cO1$_^bXXlx-;7 zMiFXo_l7MX>6b*O*h2ZJvJ`67lH>3O?jm;?`@sLMHu)4% z;|4Dm$?|}v6|fh~(rWPT7M&k+@gGTxMYDn#ah7sjy~6QdQKVt(qoH7FXcN-=4=KzJtoJ|IZ^#UR$HN1c zCP>Wu3BN0TMkj#Vvi3yMu$ywu3yDB!w;nGrgK1Y913Ivq`T8F#hf?T&Mz^O;BsjIk z=G+~O@z~@%PNW<4BU{OxYklR}4gn;0u?Dj*+O+;OAbIx1HZ=SD=4$2fORI~tExE0# zp{2jBSU;bPRJzUSA4zY-+BDv+ohi?LWV=h4n@nxisl1=Qo5@K_Tem(}zxD(M z*&j)t`=PA}bi5zce5f;^2HQr+KKq%7MYnM%>WeF-x~nY;L5eUtMAEs;46FJ)g~y>? zX`Mn_;f(Vf6Fr}p)V?wT{TqO9z-_gunrC4R2g%=k>I8}C-8?UFc`zlq3|Ct$KE}!Q zgg-3K__oALU+yPNOS`(cP$PCZd(W%ppXD(JKNcQWt%IMC{tTQC_l_G*UNV^>r3RVb z-izt56}nKz5pidQ8!!T@FH`k7T1|-fOA~MO)Nb1t(A{7Z zKx6`Uk^8kj`z7fC7fm*5gep@#S4N_4a6XfFZQV$Q z0)?lDpOFZ!`WCA(=2sbp-(IS0F*>=frKB!+J9WdXH-!0_TuFg!=p3C_#l1{Hz7{xK z8TJni6pYS-AEHS3sgp`f&G3{A?TX{E?id|l&hGaEFH8{RpB3D!pw?rHKEY=tlC3;j zr|oM1E4*L`*{e?eW?OmmB&qS8b}m+Of3XU&ONH~@R&kv_l3tn=k{fYbz)=0g#`r&8 zx)_FUD_3~Ux6hce4^+RM`WqmjbUoSNRU*gzeYGVI*7-gM6XBoKe>FKTN-zV;O6kwq(F6F5}TzE~%GTsYVm=njptnNC;=}VyR&p zMKb0&=DNm$GKft5V;j(~0}AhO|RH?@SlxVlqO@{BG+oA8)Ep!iB@e3~z3COPx( z`K~-}En@&*1R|4k$FiloJb2u7N$)Y&n1nC>7KvQYB-{*>W|%oVBZ|;d*6#1ofWbv$ zmFY%JY_`gRKEjpj!fD01NQyPA@UD2iyW40AXw&1)SMy1>@{t_IF#fpDzcn2ugciH% z{gsT34(@xxvFA@G3{XthVXd@Vch^%Ih`_~>qJ+e<(ZU#xJ#rZ`Ca958J#s7muT1f< zCzt!+}TZg8-cYIL9bHKSW~&7vcGu0zHnPUiPlGU zu)ygmW?B8l*(V&27(}ETN5@=_VOr*gH5&Y4?oX!94uq6*w(iB3kqA`PXLALAk)S`p z2wEM=B-_MoIlS#wJ#Kbf)L-b*8ETBYHW+qt>nTWTx-k#=$v&fMoldJhx=x5qME)vy6nfXahfA(ueIQYk#rn!$} zdGl6_-+tmu$lXybYqds`9CzA_JTZ?A(1erk0U86?qPfj|BM6NB!MK3Oou+C4q_%oy z_esXsVU5{Sy>$VR>8&4(An~1k*_n4p|FgVf?ERtdDoLBDOw=fOF?$HbCx7Q~LvZ;V zirv=?w^cRNY~kH{H_5JMBgGP6W^2UnxP-m}69FcwEBW?e=Vv8j{WNx;E@SAZ3t%H zSK)|&U-)O=vbo+#2t!l*rlyFn{@imwU+n$*T0B_#m^8y_z>;+C5(FtVAN&62ucKSh zsYjKbBJjsV(sJ*=u&428EC{tzdS^U^izce4BcA*u2%xB%YVs9hVL*h(-ya@=>?C-= z4CU9a`|z~z6BN|Xp}1_&b%bI``jCDi(R+z|9g>oSJ7|0_AxVpha0~plKTO3?l2d`Y zM_X4c%r9gWB5!QfIiIyQn!$5~tD2I@eglr=CXcV-B8@Lzd`)O!jPwssBc96qs_>;6 z!$MGBRqxA(7!aB6tdlAYyf>HakFH0E)BFoR*0p_5zVAaOcX4YdryfT9`&#D`IAqRj zSTKYZ#GA#^>Bh3=%Jv`^QQuV{wBw$a>;go)n7ta}fo)X~LE93I%?h4bs8F7)yC800 zfBb3}?0+fp)L{t}nXopdw)I!8SuNm0Y*dR1c!g_86v_G^p|GO5ggJjmpg{qq`#sZH zy0G@QT5O+4C<^W{-c5S%_kW<66(K(lK75N=VUU1I#T{n4(0-3!9h@(N=Z zu-tDXm%e^I@Ee(H)vIB|kosDOwFa|8qKuhCom{P^FlS{TlUl<6^kagB$%h?v4^lC_ zxQLV&+geHexAP$|UPA7q4Dui%8E$pNAN$h^<1wbczbo8Y!8=--&C!K1yX|!6$#$&5 zMf*=BD}zwaazFF)NBH=pBl5!JB5?`jw?@QAu+@ARxsLm|Knu>9&sc?+1ht0g6ur`A zM$J{4&9Cf&3N&%1SDCKbc3?q7Vh#%oSrcj>^O7!pFH@wEs9tG1V1?V=!%OWAR5D*> ztN82RwShk4Xs;V}!u#;98`18hzkXbtV;0joY=}4rOB#i@13FZyGrTCpdqq+v)iCcl z^)7rz=x2XM9h3FY_ws&G`M{?~C{Qf-3XMFI(qXP441LJa9=EH|Rd9h(;&N<>&o7Kd z3Qv{y?l05Z-A((SV1^KZmVrsoI@z2I<&^I3Q-PmD%K9cO-yi@?avnWa$#yEbm>qbL zxDYo>RIGVO7j4n36;&(}cAdzNATlXgh`%X-zB#`Nx~ciNrvg)%$y2L6*pUmF5dBKq zO-kwMR^!h+kp2}jQwPlFpeBbm$e55~I8lu}lz=Aw(B5TDy{>4S49J{RRh_Ytu;rKU zaB8z8kNDtNL&4T|%Fge0Uh&TI=g$%lnIwmCWkv8CjpO6q%_wc$G$qS%TXIB@>#4R- z3hY1Xs1yC?;lzWZ(uG`*O#??7IH6uNHQ3~Z2fK)wT$G!=W{=Poz%h@T64jg`7|*DP zsk7(75=z+JKnuRa!}W38@F!=exj8S0NKBT_U#&EAfNri|FZ!e5*wu@n_t6kd(tVLR z(o1A{HEI9KRBg2f8H{;joHl(P+8#`kB}Y%(=2@Es4sah1mtA=wDMAkZ zhK#=u+pj-3!c1)IHe@fT?>K=h{)H2WOz+TNWD#&cbRN8rnil#Q`Ua|^v&m03PV*{i zCafP3Oz$bl?T_i=l;P#MBG11*267>PWS{wR^ZFFN{6kNR%09PYaySwYf%R&QVkAK& zRom^BB~MVSRim5vPJ!zy0b`Ar2dAh*>M)2*6f2?V?;5_m;N~k>Gi}*%uwq^ESls0N z;`PW*xxAuoV*a1$s87Lg;{szF+QA+lV{~!j!w)4D)Ovzvg-5{Zoo?I`f!a;pI8G0m zqMNAkks%J)Uzgzb4HxI^S~XJKRfyO0JAk-OkVc)fhI&+crZVB(xpCgGkoj}=lsWOh zF1m5js5yks#dv1Q{~8nJeyUOF z1TV08M@uj5%hJ#9eoBn&M;yfP1&(RSk>-5@QiDN4YL~3X{$Y+&3dFc1sY;0USHC7c$Rm<~lhFpV^}J#>@KyEjXmPQKM?jE{2ZE(k#;E<6J zLC7_Gib*f12da7D5tt=$`N$W4ovGAvfL42xU!FEc+EDmr`yqjR$D;oTb8YUdHah4o z0;tr2#BSH^*T7t@BMN~(yGj_#l0f*u=)qZGMsD`C?JBhmh)g1R>dlmAp$XBlahTmf z54u}?ebf8JwZYgu`sa8r`VyY52md&DEwu;P2NL;eDvh1w4C)L%!W15gD{2F2qtJei z;K%A?2vVsm;c%X@hUL>;dlt^3ynwX={Pco=?)<&xxtyj1|8WqR__8NWf7XcRa+qAz zNtH1fT;BK?zj}L8MaPNN>+E@x^Hk914^sk7m74WMtWZl7R(B-EV*7Ps!&xU6I*m6; z*S62NP%)66Sc20m44zg2VX#?wQHf>g^V)X>1up{A*vt8mwxf;(L?%;3ODzm>aeT-y z_$-S1H?T0P6;Ze_%}5zBOh4-4f~%f9{0~zR!oBaz_>Y$!covO-PEOxB1?OAleHvVp(6h17 z4QknE!!Mi0fl8BBn6d6Tkz3-0t+rBIaTx@r`jB%P%5vTxg5Lns$kW3ZC_Y|rJoo%Z ze@qR5diK_;i;i_^0Ec~tx=Bn#q)yAyj#Ql(!1R0AF?gtwnu8}Gm>}SaHZ?8WJF5aY zik41zejcS^JH4-gNCK)gn94@Wx^&v%ky77a=r){nHjO2fVZU@mAB@hI)&U?QdEN!5 zuqwA}S+AO-jPIF<(z&>k-Oq8a8)+AT#X!h6J%xDxMpCo49px3R_T*sERo1Sj2|9I0~@(sya)}c0^`WSUB*UP=w zfXH)JdSwBq6}cNlH~J6=QopM7qWTqz9}8S98O$EF&%m~9Fhz-Qf*pfxacxgfeGtM02?=BdaR6NkaKH1M|81Rx^q zHy3?H$a6J=Fg+p;+r)hg(hDIiMXR6NnZjYV(`*`kT77?8&LpLOQYdCqtc??wjRU%h z$-7Get07G%a3OlSZl%+45tzfhgbsSxak_;9F#X$Da-c(x$4cC~RW{-84Bk@;Qh~bX z^|g)aN?o#OLiuqmpXn@1IpVooqt{^a5?q8*CsC+xsr0YqI%$cE=yY<*ZselxOskNf zmHCdbk3Cj-hmou>79$dVK#)Qp;x&m?jrP;6iAEf`_}SeO5v>;DZX%S|Ct)J9<%7DH z8&X7oOzS_%G{k8+#zsKZdAA|st`}s4CY%34F0dtY#r7}Lz!zn>FTrg}Jv1?~R7b2% z8m9IeHtyP$E#xNKcn` zYfGU4%0EMi)%i1^R8S_NdLP0@5;-(1N*@)?y?Q&hck!3$`1xtBV@NLEo@C6_*}i@S z()-r~(>y~DR~t~rS9}#uRiov0S8pi0r@aL8B)(S@PbF|sz!yg~3aTH;PdDdA#A5z5lrrw|Ld2{5Fw6FjDe5F6j{l zjH&)KF&gqfB*U&f@q`qxB|xfr@l#Yo7q+6Kwu1zMzgWR5B>dpzbKWm*x6Ar(S^Pjm z3c?uLyfMUe5y%QUwutWl;Rqp{(TA zm8=D|pi76fu?%8{J9;}HpjY%pk#o~vwouk@;$_?knDI&VN*m56)m(;vW}rq3i#v!+ z5M5}%e+B-Y9tCqIHsPQ52lriBnR@M+4=>AjH}(OQ?S-F)-tSMes;Gs3X78>3`RpfD z!_@RkxJ=pWzZWyOoUv=4xxdEF*{HKzk&U*{zrCSGU%`kFseUC+es zgMXtJ%WIw84XZ}DqGX%#nUohZT@x1^jkjpg>_FPPq(S0UzuPNH9MQ7okk1$*<>@+! zAm|&~qZL=&Nl#=&aY0m05SftE zZCti1lSVLazx1cZ9o5Tclp6HhsDU!OQgY>T0P>((II_?+(1Oq zyJen)XhzN_YWotLH9D#m^8OZ zo4Jzz`KXc%wvwV+EUDhY`=sYU9^o@~P8dlsyUu3f36hPfhazd%V;H3RnhR|lYRB!n zx?B*EGByc%0t7`uzZBz&<09BUhs(s5S<{R-KRob23pB-e+VT8PrZ!Zm(QJ6-8V%9; z4(PSzj~9Fco~y#i-2Ct&VHmyRF+c?TO={02BBn43X~S`P>=i=zUS2vcv7=;aUbMsf zXe+2P`Dx`4*x{nx*C4qi=CcKjz}7s+$)s?@Wk|6CNw4Ii>MAvuvhT9 zLk(nlcrrX;Zn!h(6}S{!`?BV%Kw!5sjOc-!{g%%8cZ~jL4h$CchgdEw;w4@>q<5G> z_TJhc$^;>p7^!xCN3+1J&fJdcgI0{OuU$;i!Bbuk@m#MB-{cADe|w&zYuiQAbgiz$ z^hI2x+NG5P4Ub}hs>c-R;?FBT@N0pUvyKkZeLhDb-%n9NG#+ET-%v(TW{`8woi!pJ zG1ZkDL?#9!&sG`!F;A*KNVZ<1#EWXimuX-VJgEGU(j&-REm$>;I__$ zbHwJpc9DvPAKzxdQc>KGC;~x>GMkWF+nU~ufQCu^g)jE%v0a0q72)>vk-om<8*~*h z5Sf6wcHg4kfB0D|1f?0hr^a6X3e)(6yjz9j?Yjri^zx6p9MO-IT zZ-JGfK0s-Bf-gsjqV#Lr4YLMtBPxEFt|Ezzmm<^8lTG!S)SLOxAf3+3O$wL)nH!`^ z>Qlo7P!<-8_8AisrIvOZUb-ElyXaA~Y&YPA%mghAlY}x(9=7ryo#JHDA9KOkEZDQ9 zjZ3J6v>mY&nQ`*{!!F(NYi25(l7Z#Ce>J%uoqo!Sl}~4xk!l$Q1rtWnQCt-sB@nl8 zQ&<#4CNWjFuLRQMrmsllo{2CEva~kYCPSh?@QA(g{UY+ts_%*E_f+7?zUo>wIn*qJ zTes{jrzJ#v2c@+AN^uOyj%S{~Js8MqbUM4#yeVXBF%vtrx&CP`eF_n18?Z1eD;?}3 zz~(so14N|N#W;3NrhNQ;{Z0vH9WCzK5?S!<=}8&U<8;a-a+UtSNC>qIRP&wD0_aOH zGoEm*eywtuYVQq3S;2-xn7^NR0s#>i$r@%(va;^uf#(bHYdnM8mWY&FU^(6#`<~qn zfrLO9HAt500=GDaO=?JIkD!Ov=7FW%OkLh-$Jd2Qq$^rwfPV4oA@R3LvYXV<9k*XT zUEPLI#<6XwylI}Sd+hQi+-(cG@fynmlD$m1Y_{O+i=sGe!L~v;8D8QxaPo}UHHeD9)EmC5eXNU z?Tl|uxUQ@HkYe5LAly-S|CVL5fG5u$SnFtxp6eH@5THZrO5AEN@|IcK8~6CeW{6ST zS+5-r@6mzCRA-QL-BsfX3t^*BqKmCH_nN$LWyhQjg)yfg!0ZF#+F#3IQX+o7*;g{G zb+s_9JETg*jdRegyXWp#G!SCxe7^)4&z6Ey;XJ^ z4f^tm=MF?9S!nzCshFxzt&FebC99bxR(#G`>scrWmK;}vnuXtX{vwG0aY47~6B z&N}(H7Cd%hOVZK2V3+gwStp{08YUX>@4(|7=-|@semMh;fic?_PfH$7Og6~}dO4rr zc(r8UKn9XCMQrVKep9KX@ye^sfbgTyV4)ETVDo@@??1v5PO=A)XX3RS|j?zjllJ)MPDy%<2GA4$^3a+7J%l~u&FNLTOr8T;;)#treb)eo;X zirrTxRSD_dG5d!2E*-hxosHywMd(p z61k`#x&g^nLTzF1H4u>?)_;{r{(ah-@+qt$2`)By$rsI6Uu@=b8MqVq|IcGPt_f=O z={qsYu=S-SvHHjgg>nDH9O9p!U8!a-x~k#B$VS|b}UR5QfT zD~|&%pFm===Vl!5wnBG@=|yl$ST9W=?}Nx;N*`Z384J0#pd@9b2bg{@QfwbPAU?t$ z&2hUgAPTUxD=VzP9pc3*-`}{29dj0?BLjE!nKUinhch=#CPyaeKJ~JbOe#mJXM9Ra zU?f)Em;+!SB0(M5K11^6wng0wWk{jA>1=2ESw9$5hD<83q>}S?jE&+yB<3#q`Sasb z5v%%Hx}=u@Cxp+Oi98nK`>*C@;e=}x=clEwV zqlH=Bqjb0NRQ*Da1@u|AYg$8Lxj*W?fS!sj-#!k0bcy5hLfDg(aB7Gfr}dg_5#J16$?}0&exh3UpNH}}rI|OT zWus2KnXhj38*>xUD$2q;`)@3k4hqh`2lxP?rsll@kI<3=lG)DuaE%F8?b})qGqF}F zgn_SS)V?v-XMo6*u`NlX%2T+HV4fmY=wbxV#M@)#(S7b8Iiw{?pg?N&cg)!^4NY^S zS|9f(wqe!IAP+a!GKTwtkJF>7izuqt9q9vUh!qQ_?eWbJ6;v?cW^BClGt0hxO#UxS zZbf_bbUqQ7f{4VoHeW@56!%~=4O>1~xi#xxYO-cw2<2^jl)wsycWs6K*L3HbugKnE zx-QD+rFXF7wX}#nP9(^7S5HTu%fo%vBH{ufa$ni$BA_Nl<@zl@THhVFD%jk8#_TVf z?zvGDcvfet2qIHb5!5ItX}uZ)Rf*B{?bLwPXFW@#=fQ?wo$||dnR23`|1zCurj%bX zrl_0CrNMh~uz_93(~GO*yh~Z@YJ1%q^X4Zo>e~D*(`;L)^))C_LpPch@9s+Y?C%_} z!%nzw%*1Z2KxF!ocE-i+|MS*#mw{&Se%rWRyhgRXQM{LsR7!cxoFwUKS^eYU_k56C zh_kYduG^~zUoL=EJ+Y@~?DkvN$ zJI?7L+(CI6t)r=#LLf3#iO{jA_hV$DIFEgsMZ-_iU6E!#+OpDa^-rGG)>i%}H&VKE zgTN~shvhaVg#BA%jj4uLr8?SO^y1iSTvz`l9a#uc>o@oigBAob)`I)upDezbqbUu` zk<+Gfoed$rcolpz41TB8Ha*xT=cJ<@aHUDe4sQ6^wl1#>H~-;RG>8~J+8^1d#CpS8Wz+4ibA^(IU~rmxZ{^A~=%VfQl{@Ng~fYaLC*(TDHu1SH7yX4Jlh#3Zf-_+omHvMp+u62zO#INO;Zg{UUba()n!h*3?H!E zZzLy;h8NNm1+C&CBQ}rp$<;Fj0rp@o7Dq`gOS!9Y{H=kKMN3eKR@r3(EH~MDa>-q6 z_vCZTbBIY!Hb;#GoQ!f$^_)R*rdc{@O(k9SXNa-0;;?Bchr8kr`^+v@&(rK{xUw+a zYoA&r{c-R>b0HtXl7OPiybrXul!j@DnN0P!j)nt#=PKV<8km7MI~5JIV;oJpO);pu zSHl@6_(9UkUJhFq?{Ckn3VwEOAA!hJLyH?Tf16i&f04-){VoX%6U+Z?7%z}UOKKWWJkX{kV-zfe~1tT(Lj^+ zda_x|kHYg5xhxRkn^9vj2INkz8>n-WlDUm{eBVf^72`bLOvD(}_ggLJN11W>-JqPtyKqg1xjNINxzjauAFFFl#3A{?bEVKz(-R&J( zPS$b;bH5L?*WDLlU!c^BB@TbXn~5U`A`?6E&Zm&Fy)4OR_SHf=Ix;D2C(OBTIC{_q z6C%<5CVnN1{9!7@%*$gxs7IL1dbmYfN4ZYksM)ydaw2!bD4y)VTDE~8bvb&a&Bl;e zKati$lBym2f)>%E-ej&6((56+R^e3TCx}dAQEPi4CwUuC2HMWpgX$mi+_;>+&OfIs zr$jjU(XaU{#q0N|lOJ@5u8o!Mp`UQ4`*zru^;HjCySIkDUBvUi0m2AF6!35g@8hdv z8nKUtg?bq_oT0?S6e^;_Jz}4za`V>=T8cqLf-wE?w@aiy_6;_cRS31vy1@lgDx8!- zL{bp&`t?r*tO#?)nHpbyP@ipTvkt#G<6d`?WFXy!EVEp^YHT?uh~NzxTgF|N1=8<@f23fvXA znd4Pye^nBC%l?2*i)RnMncoyi_Oo5aR~ieR!bAEgE07>IS6bTpwfy3Vab$EW*y7Og zp%+kyqQhFnT!eY1_E&dZa81--6=i=t0RCWS*%M;Lv$o^9EkDGaxa_`~%KZDh2T$%n zwzPSIoerJYKZg|ZlclQbEzYM9tqQgFb5Cm0IMjdE{qV(2EVomK-i?!D&9+41B7K~pIMj@?tTlA;t9#jFJ3GT4fb*j(^>n-$RcqT;D!SUS* z`6K2V;TsyJ8($=j@F%3R=r$cYZOypUH8k*?>cWn<_qXyfc7g zxl%3?(QpIYs1nm9$~wRN0e%&`ZXau=R9mW#sIyL$Do<|tN2c)^!$8zJjR^^+jb1WV z(XsMSM1-?`betQABicqxdbwH9S1d$U>N@aCjKyp`I|WYN zl2E-h)bL$o9@(;L`PpkQl+7xuY3{COvb-}W!}4;##uh>e;|Ip+D67RD*ntO9-46Q? zo`fZX@X?A5<+?Tu*;fZoGL^Qqmu+@m``UV@6erXt86`=mDEXLxGR+Du`lGSg@2R;JQ3GW6;u4=J zHp!)OYOR7}*0(Ot$I^r7PC*0kviuQT4$yTcNT{Bu&xAcI+2bu7ZF|5Wy(C6;2LF1v zvtiVyDJ5pZc4kqY<8-P~$=BrdOe~qVtx%dlpPJ9TfpRU_7`4nVtw#M^l;iX02)EMq zoqc3^t9~FKUs$HAbrFGRIf(H9z+ZsBr{Id_(p1%L<;&0pe`Bz3OW{}8c$rVd5#jm% z`@~qDx3U_;rF;MQTTWUS)bJsj3W>k7FUcU7K#yhN_eU#f(I%`I$ica->G62Cn`l{R z4#F73Iq;{&|wn9jr7{l-^v5_D;4hQnw4Z zm48m?lDGNQ`;Y!pz?Mh$^xMc+3l!dI-wYA0E171WsheWr=^_p2K|($Kw%;qZTh-dp zV2jG0P4Qy{f?i9KM6h8@>r{yN7L`o4vV91U2I*tMR8R3aff@qu^p(Q-z~$vbp1*OE z2pgg3ThqIGtzZDji;`4T;4A5DPC>gEc$OrLjT3^vypV4ji4pa(V}Qy1lW+DJi0BFj zs;k^o$cXgLdb!qd4B2HRIH9oH)e5~Ygm_w`{ves%@-)Rix7M&jg>U0!bx>u8@$Rpn z2ro%|&!hvxs|GyKJMqvt+}aNa1)=0$$y%a&ogIj=b)ifnMh0R!jhCUIfT&WR%*BnD zebp%SEAVO}D|i?ULtlj1u9Ug**p6It&DejC>}~GJs-b8PDCol)+N0C^T~4V!^6=P$ zb*vGZq z;?4eZj)SL{!Px@b%DcU96m1x&%P(F1h8nIHF`rbbEqmPiFo#3$U^3XwlwM7{#J?&v zZkUztqP!bAjG_OOU-Cb18ck;YY!a+chDF>Hw80)?ZxK?xxKhIHD3EM1Jt=t)2|DtK_MYP#4V-tqPPA^?d0f*;GI$9bTXJnvA@P%Y?|A=V zDkt(WiDYstgh|Dd;S?~UBlTv;55SE*YEfR@wFujA0dkKKqJ3a^8(I0WQ0ViABirS1 z86u5wk{`@7FO2xzk)P7WL0MH2rmXrZ?Por$+-B;awD^Py>gBoRR|*oHl#T3^3|EQ# z!xRt^Jiw>S(_bD&Bp_kmBgJ`!HGXl_^ZFB;!so8)_ijLPkO6p5pSP8>&t-DnamwLTjb@Z1=6&#FIciVW_$SzKg*FsM& z&MfQL?u~fQJYi~{L-}m)bbermgouN_SGaw9ARkui-y$rryVlgi(CvDs|65YQ~=u8fq7+xfAgT>i}`s z2`pBU#lfxU`_mOmTI?O_Ex&RvR@8U#dq|nNiybgg`A4UzierZn2ZR9>SB=OG7U7#$ z*y^KNR(+f&0ShLg$~OjpPALncCm|zwONfy+qeoFjTO+(;(9ca`E4xJ+f~M1+&STUS z#_n(}coT`k#S1YEJL`|@uYYXC6yIuV3XHf2#$P?@gp7^6AGWvqo(fDa-#G~}yI=j21}2oh#{)>B4QsROoJfB z>z8fpO5+ZfDr=c7*Y1aYen~zJp0uCWGj~j*GU^TiAb~PrzJkx{+JVIsPOpSv!n8o; zsBu1IJVDf*JI*&3-#EYjHyCYU_S*K*(nRuBO4zH^=o^j8`KCB|`T=1ROryJ=og_H~ zDJz7*l6WN&wb05ZN-f@mmzxc4W~z#n2~^*|E`oW!vj>rh5NRz#5-Vu7v&m%f5_ea7 zHfoN9dPqUH66Y{}%@D)uAJa{v!|&s5+k6oYPOv2YnyP>iKV55n`zFMIaKpi>XS5e6 zo41iqTN#dFQE3)m;V7v-nq%xPe`vf(cP+{`(;vzOJT*Y<{%q?R4r>avF&K-*!ng3m z7E|q2euz0fYP2w9m}De+GM$mR>WnkWaq+&TP>ewQsfxH;0Y;yVLxZ{AMh!m9$|Jb_EYgu-Qgu>bf|PV0SB@?Uu71jt9CZ7|6}Yd!>W9q z1}ukAQbM{rq(fSyyStGtNePkel2h4X3xu zm31&akC-L{P2$OmRY1j1?i6Wp*fgD?JN+g27kDybHfuz(SZcuZ_}1;|JeFy%k5J4V z3sSV|8wI=b?t08x=2s7pZ9xsGfSkmxxbbYRetNFY9!8Q4vZ+jxQQfmql#_}ee^g}M zoazJXu3taDEUp#)H)5&W$SWws^*(|2CTJ%@>T6clc#OWm5HQoe8`iEr5r&T7n1FF0& zLr-MH?VU`0a|m~KJweB56f&pM`R)QlM3h+F5u@5Q(5p>TerCrI2y*0oz!lOjDr3lW zb0q!~%{?XR-6WH`lGTO(^?PU3#9FmuD5fB1weF(pptyj~9HQGR`yZ0*BDn!F>%e;) zZR%?Q6u&c$3G87E$_)o%VLRnP+#}#60*ZH^F{=9e3I%&;$_o`(3W8Pc8uVn)AVCF5 z{FXM$;5i#laZafQF2NG2lP^|s`q}P#E2BslRDRnbA|R>8Pqwj6O6B}RLL5j%E#8JY zInK#;@iu2X8hB3QP_hX!$oEwuIXJ5k2F5~ddY$*MqL5+^+Gs+VPg3g+KapjaFRz6m!(f`*}GdSV7fg^=W?a0255kw85@`MBo-(=?WadZrR7kODui& z4KYzcf8yg>o+mPGw-hF+;czg26!qD-mtv_5c0^j}HNbMafO~adb|>ESi0KQ{i-;4A zMr31qru6C4ezIot(JF|n1*AP`rDB#|uXn(8br@|$*<1e&T6}(CK^AS_Zr&BA% z;&slLz}1eWYHD@b_>u9&rDZkIsVFO3qD~;h)F2fd`c^P+cMIKhr^TQmvg+xg)Q_Ia z(t_|cB@TTB7!3KOa~<249s0t!AFjFDa8x?hv_kc5mh`VTmgx=inv6W4e22hHfBnGQ zTD{2;mJ1k*>cjlmJRohgjWhqZ^UpHfW$0OvA!#=>uL}iY?wu%3}4|u z1d4+3C+-^?2-EM&Yi<_+Qp)vhUqtOY!|G1@2}R*4-KL$P#c8o&1B?mg7$X0xL$J5} zC0!t$ANiqqD&4Dj zl#?dtQa|F)O+5Ir==S&d-A1!)LyDOCwxu8a}{LHWqznfvFAj2&JTX_kr(v5&h2BIu)hWyf!I#V~qTbYIT zvMbM1@R59A~UL{!9`Zx`$*W0Il?ic@ah+9no!fSWXDOC+axJGbL%R~xLpun zp=%1qDOscLY5KLuw2s6%(7gW~Ym$Y@uYB`^Cg9iSf)|g3?#VaZgXUU2==>u9Hn37F z$s%5C*PB0v-{o}RTXTF5a9L#0d6%}2lT>4pE07!YvUvq`SrZe6NPS7SD%_KpHhf(DqvyzP2mvikTu?R$Tdk@+=~auc2U!*f4*0p~&qk{flN&dmmU zy(c1R9@frs(0$}wgbBj^O(`{P@O?~&&5taMVi6C$Piy?q1UBT?p%&KgBrvY%o@LfI zoKzBsLJ8xpBO^7`A|*{Pc!8lU40ywb4wQ~XFQ(gd@k3>_ zo{047z^eAMt!cNhAPPoiLhd~K@Uc&|j|SmgOMP#EWxh{2{J(}-{5r%y(9+T_Wrj={Sw_i=MwF4IfyM1 zJs=L-Awk`Qa{AXMd3(ZuY(-jXgRNG|aH$lV2wPr%;Oyy&R!a9zOb(TDxURLz>c+x^ zpAsGQYx>1G;7A+rXf}0z_EbX(7XlMfh;1htI<;?Oc5{UdRQ9Cf6G@U&=cmupVH_A{ z*J7W#<~ZK`+<}!5b=zrLwcHS4!G2>iNFH6S{jw8;%gTG^&H+aHPfXGMG|FKtTtWrd zGAgWT8k-ez9Z| z&*;i=o+zeg*%GbARZI=*pd@Wl<-%(h!h_2h%y%fUQcttmmN(ujNf8RPy-%F`SgX@;K5#Vj0L|Z z)bn)oj7;{4NI491Fqcj*e|-;6f>XM95mDSQSdkZtRT=)dP0yeCTfjffLB#}flQ0xj z>WF?%;|obJs{SDTf+~(T+r)+MmF`152t;HSR?XB&B|~tE*9*mX>n8Pkj(Wm@@)v9s zS);l2mKxO)k@WG3v^$ikj-Ne1KPwQfc?sdr0~{_f3TKIQi@&TZK~=&@aTI)W_^m$qV{#YXbqNxVvD%DT3u7 z32!d30B*>8%A!kGRmv>2VfxK=@Y1I5E3}B9{|$qx(-+^f5-Q;{>!id-Oo^{u@f`Gy zjcq-*sSs#mr)?ThZ=cPugpJU+&i}@qq64}VoH{AoAd;OT`dE7Ne$EiAM=1*CuWh$m zGAa^AhaH2Q3Q2zG*Qv^pfP9bjjthUeT)|T;a2?7F@t1pigRfI zd(iH6%g?CP3tcA6;h6PM)(rTVjd1Vj1)yylou%ee2LBk%T`Fnp!CXo#Ty-}z{rKUz zXd4RlZ??PTCn8Zg)3wGS%A!oh*F7t4Pb?M%1wh%(jAoZ4o}{q^dVhOlPW3vN2Whc> zg*%7_Hdm^7sbUPy9%@%uN#vMq+E7Y3z?s!z(W-Zua)9C-u8HKn(Z(i-&632cS1)0D%(IhhKf{KM-!kR5^MD zJFB5{Thvfx`a~vuv!iAyiTtldDWaTsL@X8=Xh(doBqF1q?Bid|&hPg=%4}w`WHRRq zrAAFzHKQLgIgi%PJATeTS%i`a#on8iG*krI>k+feS(CKxa(kuEdnYCk4-2ce{mrqr0cIHD7Qwzw)O_c_0ebS$vin{Bxzdk& zpa|*wpx{btQ4H@rqMJbl{8AWfDCs?s_zrS^EjfESY*3*Q|;!Ey(jc%yB@NZ{v3oJs8e&+8? zIQ|^>sCSM&BIBeW08O&}cSUVth1m24@Wir)S69=U4?~?;3>ufy6T{N{vAlFbJNs_h zJ6F7(?BKWDp5mQ!_EB{MN4!K|`LQZhhM7+W+KhG#6LxHDxhJ(o;r1RpQr(Tr&`T_H zB#YzW1#bH=R{1LRIO$)l&Bu8dGS!)ag+O1I70)FO8vJMm<%&;vb$7j1cVYb$CBk;n z4dO3oo}Y7`Ph?vD-AB9mRzIus8-s53M|8i8B(zP~A3~AuH@W=^&sI(zF%_{?FYAR6 zAlu7D~g5Ws)-&j}FrUgZ6Z>kE`)ShWU1EY`G#%f8LP;*T77kpKI|uLLH6 z9#19Ehlkz`LJnhh41)O7e`mz!vd!CySQg1*aqz3<-BxgmVIJ~SAsf$+MZ-|c4?Ct9 z9{H6(v2K1rkHyXT4i`)0Z1#;VC3ot6)4XS{5cT94H)cTAP9Hhg*G@I5Ois3a;tR*O z)C09|2rtBnnL2)aGo?_c2>^ud+nZ`IfQNb2pAqrV=VfY_Kpt0vwU}(WGD1q-7Z*9dRz=G!(T^CH$qOnJO#^O;D(X4gx)t*rRn zSIsE7E8iTZD~6$*s}a+G0F}3x{gsERzF)^}=7pAq+6u-z&B~7R&ZGu6%IRrZN}a)X zDoB|7?t~PKBa_0(FcHEbjmIsqh5;$sC2iOntc6qLM8D+9Y zW!(u+WFo}yxJ}3l61OYclU9F_hrReFzk_4~Int49U)D(_YyxKb8$=6R-C^l+&TOki z#hghNp=xW+W_?a836u(|SRfm(_1yefQb@aQqx~xQ%)+ z2VM^g9M?ZZOcErWCF>!`X0-=INJ30H))9*aR2!+Nrv8dE-SPpRmj93>)f%7s?hrW7 zmclxfL+Rqvi*oZc(&8n{!vqIVNV0VSQBMR#TmsgWlt;N_VUQiI{WCff8gka!iXpuR z_STDon@sIvsG_6rVO$I!j$20I4^9qXE=!xK}5RuuSW?CsDA8)-;{lRug&_cj5z)Rr_Sk= z!IyQWqXq1KpCTp}<(F&%Q++Ldr{62XwXTTHp%cvI2B)+~4kpabTHc=h6BBD8o?+w| z-LBE?icK$(GLJZ2vhEx9k@DdYuLqZE)_FUlO|JF(HB@pTIJz|4M^|< zKTY#Lp(lZCTbE|zCHBrfo7jB@RWZE8YVY@l8QFz)CwZRn83=zZp63vz1sFCU+2e)5YTypk%A_nz?mF_#e~Fs8YQo=bJ0Is6^e^TGD>!_Mf5c1RF@2iuR2 zHsP=~(X&rPiXf?DyQcnyYq5P)yiO8&*Sv}`QE8&;`@O0*6ecrUI#QJyJT9X}4-b=sMhP-(xe*L;h zzA!OCd8(KweVy^8SL7+inT8?Ey%0UC9lX!}H`~$mYKoe57v0$= zJ8?Gy56v{m|4__vOy}8&%c`DG&X6G>rr(&n-)Jr8u!>q=n??6U`r5lO_2L98#ETl* zIcBpX17hO-jr0|9!O^%PYgBx8!1vRtT?Ot(e0nuX>If{_H?TNhYjXT96wl|5(AteznaswbxN~Y*y1~-5Dbi$Dnu*h31dS)NzGo) zvl?4CIr|PYtl+fsvE0{Vaux0+^cHto3?GTNiYVIlhqpJFv_-0bhyO#uRx~1Oo9)_# z87F*@kE>~DmBDdebmorTJy!~AGV}!_nO%!hsh*?lv0t~#LEc>BRNqHlO`#wz`k`x* z7s-W8KQiz`%>rX`^DnhD;SM!3Y}kZ#Iq?Dx+cD?Rm(se??<&$jL}dOqyvsgW6UOrc z+Eo3eLR!`DNMF4f=O2iYCoghds(hj~fq0;O!CvK$)2QwX&0cvMF=&WvCI-96(05m9 zc@S9K+R#V*zhA`sO1O&f%0DcvqbYo!)x)PeTpRf)T7k0CgDz@HJ@?*xI8KajCTLx* z$KP|lyU9C_aSce0`ctL@BknF}9gZiYCW46uYb zO)!)QjLov#`MvZh?9@f>R2BzoKPj0;iZ*9KLLsaHQg?DCdiB8hE2RDl9}jAX?G(rXuq$pe(fEy;7|LH z=`Npmh#xzC8qW*c?-g0R4x!S>ISt$4YFL&TDsAr_GKfesg)d{(A-t?W=vXu0h<|aN z4Mp5Z55gMB<%@Obi7)sRGzf#;`;z`_!-34@)_<%G3q$S`WnWwR1``L&LcenmBbX_Y zm!yPXCdf)tQ_uG`sToJC6;V(EWnMW&#cOmC!$EB@(_iBx9d1}r5p#J93L^eigqYzf z+|sY(XJWNDdG1YYo@P4?^!_8TE{NYehsJY#?*X0S)y4T%7opN)_tOm{0YiRp_%?v_ zCG+=>ZD^fPlf(y&OKXs68VP|fO`cs}AuXhOQKH)E!1Z4F%gL%%fJ<>!p~e(g$ZYUl%X)*oPdTb4g8Lw{+qH(qc9!Gfr}A zprismNRHlIoW4xQFJ_($n6UjMB+&7PKNYio+zPZJ;hXp0!llAJvlS8CDS2lDC4fii zJtqYJKb4k*h?{vdSzF!x&QSZ=EB`Je?Ma14Zbc^8@HBVK+dl82Xv(sOntEbw4~^~`U9{{Sa%yD%FmXVVb=x>Bk{(OHA9L_ zZE6-=87vj$e2YxnVA~`d)s@Ukn7Knh7&7&8y+n(iC0l^|M&afCwq4*oQ9CNwt^8-3 z-nr&5C5(z%iJm;I%noAO8}PjWA=$$-D|AQ8#w@q#KyJg^Eg|uW)PLLD&UdA(~WqaKfa4z%P@9}YzcxaMhB$uXwjVDHA z2rkKtX6#qKW1CC9I)5UQYd=+>0e^AFd0V@ttbn&FVuKCR9cMIDFUs<^Z(Nm})c>oF zxyXdukc&l)V8qv7s^Z7`+P0{qxx)hijCl-HCEIEWFn-oytUZ9LHb2R#h4F_MEV7?l z`%uqL(ld7)bIyM2RiH`pNvfNM{IvpY>Dq^0jo}waToH#y&WS1LL5YHE8~+uX!8R3K zCitJ~4vM+l!r-@|{Z&gzQ}d$gq^qFg!c45y$`n6+Cdc?t1dLi&T{1>2kIx&#yA&Y!349l+o`~x<@Ppph*gTe8SV{3;;%q)wXeEMBb3D z?fTui5}m|D>(lw@4+ZZb|FWo2I>t76A`)okPul-)w4!|jjzN%3f)%RZ2s@&p^^0x4g+zMBU1Pm~zawW?v7~M-eDBA!A zo$8Apx6pyID+?B{7|p-np0a-ajPSaRbUZ{-{O&@zHP*y$)Vm;Q_kt0#0~Hxl3(x$Cu84qnovA z{-nn4`#q2^J9gg^ds7IJX=jmK2_%s41+@=j5!mrc`HuEkYaR3)PLzT~sB@G?BzSMh zTOCh0OUH=s^ZGGYL=T5EUHie2&UTAcFC|#)6K=$X6+8+ICe@3h z=oBZ;$$z9UW!r0<uV^Faq?e`1fOHTUp6rs7r~WRh7->k!I?sz;r2;c10p z&hj4C)ISxJSo9GymC~$opAo`h zsuUImo;pY_wB#qMjz#_Elw2#a$IjWg&G)|19T9T`R$0AHTQ8vbi#=}C`lIB{7D|$G zd{ua`>Q)EP$VW&VS$eyjXN!v8-eNFum1j(y|ChE zH-VB-bhg>sO`w(?GK_eEj2N*4c6Lpm3WY7)p}?yc6Rnh;Fn#j+*_XX`05S(AJ0~!q)`OQ$Gd?ZMiHVlxh=yDrM?q#yVYYz4gX(?p?(_wk)_2^wjG9$F)lr~U(3L!CT*S^M zSk2KBcI6cDC(*iq6|wGy3UNpYqV67Wv+^XQIoXKU;>fUh(Ps?7VI_hQCDvSp?@< ziE;_LGZL3-A_J@L0yiO8?v0zH4Wn4WPD^&X#q&VozIZ<>vqM9GnvD$zajc-w|f_z?K{@Bhi^npY5s zw`P0($cFmTs{hUxY4gKq+@2xxy?PnNji~Jy(6--v+9WH$VR=n!fYBx{xiPzfxsCif zphSUQYQ(Sx2RP+FNq3;jOt6Rj>sBV5401m<`0}m3=!5WM@LZqS-w6vWmiC+a^Am8j zV388(JcpJU25rNu-N-YpJrpvAUzR(*H>?}`T3~vD&S~u4{i~P#1<+Uu8j)8l zVT${CXu$u9j_JOHY-I$yCKE2+K30w^Y6rMG0+FkByzE`S`_lB8z2PS|*K^W{!f*2XFGswZKON3Q<+gV!6FeddHc`YGrQ?5!4~S ztJp7qKM;AhdZC+yzDF#Z%mY4Jk4uEGv~>Ul%i9FYUvHrl>G3m^ZE<%3;_1iKF_V%U zl^j2tt^M=~Oas+nKd~Ht)gq1s(u)eqpmJuy*r;?$(j}u8(9Pp_CORdUgFWxbZxh@z*5iS> zHiG>C(*KzDMag4?m$cLuUwr&zv!c-+3}2Z0di=Oj`Dja>$bGXDMD#XKWsaivJgixW zId(6bZ7m?hB%`0+KY*R|Sn0#*s13j*WQ7vzlZVHvKFMEmW;%WKUcT9Np;74vMtg`t zoo#SEn2FNj40@xhf1Y?vCYYvStmP9jMF_GO>Q~j6=1GVS?}dL`p5%N<-qA802dDqw z^lNiT!4fSiwBkX*+FG|prA*ObHekMkOIty2FXI|3?uRJ+vo&htLJz!%j{ztCy%a;V5oM-2E zLLPdP2=MJ4tMZo^Hnkm{%&yI_FG7GGWBePK61?t@UQ3f&%6nQoh(EQmnYPM{-XjbO znO7*bUIOM+5L)C%W4NZ7ts3mqj5!@8O{}I9NP4$ zsl2gm0(bm3f+3$al!*XL;wHpjwW0q>=gp`VDLia`pJL7a`5!8OFwce zoMR&9yC~W6JnI4YRCpeay+18azPsb~s5*=w9(VAvh~P|n5P``~Br zHg^UJDR;%dc&VMVWo(7{E%>gLqCZeJVtxAzLir}p>ESCz)bp!MS(qlr@gMzknvJ@i zU?fcJ5+b=;^2!YBk1Nicf}e;7zOMK@f9c^UO*^6k=^hRi(_bX<7R;66vFe`JD2819 z+%4<9>Dr&jogVg`4l9OI=o9mSx}20gP(^aFga(R`9h7SMn&RTvi0b7To}&V#7}v`@ z@aaVd6{TS})T-~fPlh6;Bw6xW-h}#Qw0XF}YkM0UBb;zBQtyYD0KT&}RGX2JeL3mM zRZCBDY#xEgmXD(k+oL7J8;>+so5x(Cl_nZ|-;EugC#r@bmWHKPhr;n(`YkK0e^na` zFr=~0tM|Fxr$St5ixg)PW5+(4juQ_9Ka+GBgUAc>%(^FvsXsbjMPf5c=fxK-xGCdV zom7e6+i-6{W9(y2TO~$&j~gYL^SZ^Ag;82Jr}RZfgXn)sI;ZxvSx*!Zjnt*z1^EE| z?+Qqq>zgrOZG8Bb1`VTS67Ug(A<1YJ8{0PaRzv9bfRpEwRL36C-Q!Gy#eDJXE_heN zk@p!VxxHirDuYsEENwzR!BkrAOUMcBKmdlpjqcP zE0hmD4oLa-JmCN5uWeFS64_#z% z7D;8~$%el#%9k1dg%rAL#bI?s9*I{FpJh`}6HaBBEJ!(^vAPJQA)c|CNH#;%#?a9j?4R=P>`LMW{aVj!tmDGu=LMYitk zmkEPA?u^YZl(Ltx0iy(Ov3{NX%K2VGRh_LS@I*P~w3eQ`&+h=fhGz^Z<)SFNJGQ-A z7dcNkJMK6yj=S>_Qw6&&l~RspANu?&?7an-nnZXnhY;h?#$!`8ERydV~5khTz@Og;KUb4t7&iR-GRmu_UweK_7+?A?ooc7AEk zN8W}32#e=*{ADC3Q_xo_(x3uLdYYCqF+0};`bKoF#; zd1hs#kU$=G?X4vQTH@O`)nSRRj9V?OH-K;Mh3>X3d9ZO?IKqN4Ge6*bh(gItyS+s0 zqvgM0`?z8B9jv-qg~p$a%laSuM|729P2nBBT(ZXDG*SB%{WJ+0Z_c=VWKJ{W#x&j^ z$-kh+i?o+0+#}#)Nso_ylwk_V8x=(`=&%IR%StBp-y7LXoaJaw=O*+!^OQ<1C&}g) zKAq3J&Byiq3W!Nq(ha&e=T-J`Q*L405kd#OLWxg`fogZ$+swmy!Sz3b#6T{8{_J0; zG0bS8NaaJ$KhobRixVL*yJ&Sz5m>woqX8!S<5pB@3_Iy48OrGE@I1oRk}|qd;*TD= z6@9v$R`eGFkllN0b*%Z)u9mE`+)R?tO93C_s+N#sFdi8~@veR~5?=H1@tdX)q#G$} zz1iE=(b0w?c#?OiSy0t|K+CyfLy(lSTY$&ONE^dC=GrG=+~{`uLAakYlfA49l;B(* z)E=9#J88MEl$o-sCUgz1mNvrS5t}j3CtlZp@i1R~u++QJQ5~Nlx?-miJHyX@ya@AL z*TTw_qTO)zBNK#RpK$-@>R@R)3`1ChD$D5t`bKPGC2oICsStRe#eUpXvp@-V1+s7Y zMfd_MYZxJ!Dwtez>M+zw7^XOPZ!=e0CL!{e_$lF@h!iUn_}p?*zp^%u=nDPN?`3Xv z{Abx&c-)T_=U6lAIq9%~wEwy~hB?eTd9TFh6765FDX`dZhVw1nf1El(qg^yCk)E)s z2Hc9Zfs@VI+s;fg+U@d2?m{AK?pTKkWyesL*v6wh=zL~DyPF&8#zX7e6g~9g-=8xYjRG& z6IMcoK4MF0IsPg+2h4Bu4XC8FFSlsX=Zd;qxS+2nBc90Q0C!l$tVNf`s2Be1^msJ^ zn*9cM|2t)3m9X`xq`BOmKE?ms@9`)-*QIdHo@nmQ*jbnhpwA4XCLYZXU76##Y$g#68l7^2D>s=n=*FQaah5S*A9C8 z2bDp;)k%kjUpFs#AhDZlzWRT2GT`VJmaotio&RC7jXNaJK>+VSx+|_c8$o|*+;XnL z2sC0!nY**qAACN3+i{)vOB*w{Y;0XHB;*SX86jP`sVle`?kS57#K~p@_8EViMp3SN zwZZF|k$b;jgZaAe!zTJE)qq<`EjRh!FSZY}xWwoeS1`$vP6bHFdsE#jbRW5u+K6*b z&_RGac_0ni9oyTX{GOfbNjj>j&Q(tdzOVb>M!f&l3&LS1U1m}Uvfa%T(Wd}nlMDJU zG2Fgu-Jb9oo25pLKm)sFZm(OUfx?2ux1NIJ-KfA%*a!;lN&niteGP5UyZI7u9`_vpf^gyMnkq zX1r9wKv(=oSWx{@v|_Yxt4}rajGM=Wpf%C8&&{*WBSRemDS6NtPBj zuPKnauKBvU8n?-Hs}_dH9K)e*zWDMZQFE;>>4`|Ih^4l=VoGjbyvnT{7%XL9(^wmZ zYltklC3%zF_X(N*lalsLRGqe8qhL=Q%2ZFYuJA;k@t1Vs;|| z_bGj=4Uuc{G*aaKvvO==8PcFT(pXL>g)}N~(CYb211n`D9lxu1;ob5#A8~uQ{gkqR z!zlEmrKN<7oj*hTWG-KF+%O-Fu*J?5C~Zo^Y!<(2#Bd^3iQs*1tW%!!XFkJ!E4cF4 zWtZ2D1f|LbhXuzAx7?pioS6m))=QL!H1*DkcnUxt0Drq7#?_l)(TW%2OJ+=FKPyoJ z>{^^L^}}Vhot^D}KT%9u7ZjKB+f$p0jgc_MUR!rXXD|b$W$6oi_wiNA<`xNHG5yDs zRWuA~9!Tw?tBzc*Q0T22Jx^yIIRoR96FBt(X%)OvtckVCHhMyn9B0?%b?a zrk+g^Wh>iNYV!nR5XBDPO}_{+U$*mFwLT<#cS~ zi4i3ssfF1K6P{%7CXDG{O1>}QVCqUjP6%;^wD3HDO?Qf$X4_oxfN5@9TI}P%&eQ8 z2v#*Erbx`;hm(|;)CshP0|$?mKDBQ zlU3-$Px3=kIvV$i8rmgvLmud|0Ds}W;^=urcggoTt>|REs`r-Z_#z+Py{-0HSt)Kk z)aFuq#FURq^SNt;>NH1{kX6Huimt9QP9Cx2E=wjtt_CBh5CcS{WQ*(n+k4AZ8e7!C za~GaVWlvXOLB`cQnaDvn2EiMgeoOWc@4j{lQIeeG9xrDmAG?S0y9!LKXZ)bf$zfxI zBo1bpNP6>ib@_qEAbp!Yh(@DEnw-$V6Y%ifng*@SdNk9A7VuO-A4h=NEm`e&d~vKUWK=$N3$s=-$V0 zrYACOd~iEkF$j`it#}W`UUA1Z>W!BCR=f4W%X(7?ct)7JHPHuVla`HE}pXq;oq`IFDunJKXuC$aX-^YkL zI=OT~{C>S0DeYqCwEDKCL?s|1jQSg2@|O<<1KdSb?r75mW>g*mkWQJ+6ohsmqnlwP z0F%hV@oqi0U){~xvq8M*!kW)HH+Wn)ULB!7I^Z#Lp7ViKC()d&8sGc%w~G>@jiX#| zrASBAlEH7nz8%DfuMc<-Ny~KH zazM5kGRE9+qWbRmFvF!|8BSX^g7oad!ersRKmX9l0 zq`c&#UC2^)2=v_*h)N`JDY5_~@l{e99f%a1=56(px%&78ed1YFKlNxwj?o)1$QgxS z|EIZ+^g(M0GHn*CM)ryIo}(y4T%-=q@jjn)Do_~+Ko-jbQo4Y2U0o)F;rrcH@LQdd zMzMKi(zYWz?ubqOjF(1X7m$*YfZvtmuq-01#)qL&GK!V-eZ6@y#bc(E_s|J&ZYL{X zBv72vzjD7bnr)nsV#5&~mz=6rEispqUTSC{6V(2vSM-lt@uHRa(rS z%6Q~eV6(Oe8OhJfv8OS%2U z5zG(MBcaqmMkaFN{PDMoNixqSMr;j$mn(kID$Q=^u)RI|AYB|iv(pU;J+Mes^Z7{2 zYx6+t80@mtE(89{_b}kw=>PV-$m7s$Xk}>f_*tJfU8!WjlM zy3QxGt@!rvf$^Yu&ZD58(%h4qX?H4OW39beD@S;EJmCc9tG1tG*1+k6r!W;3T6}en zu{A+|EhGBH+4;;g!nq@)%!|Pm$L;}_u z=5JY5tc`5!+h79g0zrZUSP@bu?xW%qwVrfHkC*Tew#?@C<%YY@6bWw3JCCpiI)~=U z=2#Ir5&5GcP9TQTX&ydL!tIunjK_I(P@p8`qvONovWfUyN@v6=cOn%SL-8azF+8Ue zcrJUA|LXis(P=B_TrG(7DoP6D z$LTVjY3ucK`SsmuYFHFh!+iNj{Kp-dW&gluhi*P$$dAPI>$p-BVK-s!D#CAnb@)U1 z`0UBubxyM2u++lxViq272ld%*Fp&FxXF#{FUvh$Fe-K)wKdXf@1=+ib(Rw}cCAb9f zDNN*`5pLaVRF>dRxRveJIW9-PqPXb2H#M&@-)>RPm4VZ*|9LuxQ_qCRyA(-hHRk?B zhLYORAjT?mtGc&UXc#*&3hqGt(gzu%Z+c7bj3Y4AOqOZa;axhr81iGo-ULP_p~Nxa zUp)~?avrC&^!_GF{g_GdTZ?^GqZcF%$AToSlb1@jjSbPCZm0h&e3MHHf$&7wmit<| zy4Q+l=cP;fzB@eO^iYa%VX(FuU`_B^?E<1jz6|G<$?S8;J-S*vA0F1Za?J(j7j4A2 zJAqoECy5D-e^*obI_C8vEB~X71N*8B=Coq1=m2vEI-2@J5j!FI>9=lEx_R0Fv<^u5s);@i~5|z zwBDJRCo&}@{pzg~3O`%cleur8`(CNYHXm>Jf-qbT)=g&i_yf^D)xq!vn6!^Vo?pldOtjcjZao#A zW3Q)?1=T5XBrzXU8GCD zdig{q$l32|W#|&Eto5b?F}Pd8)m;?_+v~gcOs#3JX=IAuJd)F-HS%UadOG#c>NyVS z@d#XK1oH6_vO|u#+KaN8Y^H5s*}cr5-i3a)L6iHDx>G8_6hqm{v6>(!F`pwmov2_^ z@QF;8ox#|HHH9?(>Vta=+)%zcFAZi4&1OIhRPMej*`e(J%!&3ZRF5e-UtnP4u{gHG zs-k0%L2+|$`V#$xtpPpxAkcd*nh-E0ioqW*P_3CIOpT58VGa*l>+W!mTgGiH_D}&_ zne`OsRAJ2xUsf3XI&Js*O4{+mQ9zuM_vadcZcvQKBt>oq?;|mJS`ES!;1O2iFKs98 zVtv2qQC$rtrru^f_KqF$VN6^F&YZ`FiCDcSBIo0ue^7f-t*cFYvccM&glsHnPX0jb zT74oDxonF-aNX8xb>Y@?7SotFm=g`{c~S-m$=``2dddf7{>5Q%bDW8bZ1@Fix)gmD z3=F;HRL;k|?C;)$7nYmULPWa%wff1BwZ zVED_E#1z6N$(B#fs-oaLJET}MGf<7@nSdg172>=i-eP4;1+LisUzpP5L%7+s1D5Km4eHC#iuv~N74A$WEg2^gE8#*6h3 z0*%dR&L=WW4%C)me4EW^*exEhSisb_XscqaGDV3tnc%6|UGP--XHBmP(7xY4gC*@& z+oLd&V7wP1FfV9&odJ6~S0VT@PwEG79!rdO&b-!<4T4brT|fwrvRA+}5@C9!-miC( z!LP#l>WN5OCJMabDtLd*G zy{60|ZQxG(Nld~Sb5SI7iz?&U0VPId=cOzghXX{?N>b?T41_y#27j3TMouWtr{U#t zj7zl#+~VnF5S`d}aX{TJ%bgG2GcHa#SHOs8R=k(QRrX`=%@R*dc=wXSJT+k&Dn=}? zS9WTYtUo`|%cUlOvntmsSIx$C0y$$SAKf}qEMLO_$4o==h6u}tfcnEdvzCi1KV zZ6jXSKf@nfy`{c-fLgQ=U~^|J^7le5HV2CKT|X$Ec=j}-9ZRMJG4n51oeRE?-7@1l z`9H?KGN8)mX`3$T4(aahPU%LGkVd*wq(NGc?(S}+rMp2uK#&yaZhY^9K7aXrdil)Z zoNKO`o!y< z{Gmz1Di5alEvBTz;SV$6U6mP@?Rjz%^cGvFEJ~5a3pXgBCV*1mC-})%ksnmtP}q5S z-3AC#R9+TA&m*NgNk2)Q0J-2NX)cwFl+?;q7?{dcJ2|7nBkx5Vh?o%R-laxtY`ifzFM(a^D*omnhwEnNi z{eLVG?7x^Dk$*De%NGMlq=n!j*m5ENocM;vo%Sy<|ZAVIUlkPN-Fja3itT^G3$ zLe!_UsSitQqQ3n-!0hio4vMZmWmkDVtk@sK)RAy_b@5o+n*rKD;;4dJ3A|&_n?6*n8s65Zlfk)_RJ!% z$1S(*BX4=}Cb2ISrywO%KjU4XULQ z4QGkmf57yICfAS`%4jc>@t)<6vYO>G&-pb&P8?($7RIr#1R-ac0F4dU=iPzMsVwBi zZ{BQYMQY~N6)+654WN*A5N)fiz#Ic({GJ2}|HH?wslL~m%Z!?Jg}nr0)wBiem9w^s zdiuz?FN5!FA9<#Tj!rV>aOdV_wA>`Y@;7R{R1PkhYn%lVJ+-Ihd=)_V9eXC-FtNNs z*A=>g`ld#<=Tr~;{*_IV@yF8Py)8xMA5X*-3DY#PgBD(!V1B6@Jt~cXqo$JTeVwg_ z3#I`_7)9Xp2vfm4^ywuP3e$Z*v1QT$ES{u?H8ESXQ|k5JCyHbP7hq(M#!}Lml0XG( z!7-UJS+v4fpIEKNZcJi<`{m7|xcW6V5Wd1({uaPe_;o1QW4Cs7m5q3YZdRs<&~Jh4 zWed~->9jm3l#)hhVk^<9G3ATZEFD@G5u2KSgA%}1w6!rbrD}_d==|^4M42jiO;XY9 z<9m3XwlBlJU8XXsaGWYDT5>AjuOriv1Y8W-`+i=Lig@+NaB)m+zzk}Ti}qUnz{S(ZJ+ z#-n*Ra34<^d&%)_Zu^#_K%g)ZE3IrS9>LR(0Pq8aDShf#!HcNH;=kz!@GgoeY%1yM zX%AOLAPP5 zl7`qwYQV{U=$bweQ^0Wi4A~?!hZRjv3ik8{i?Cf2V^Le+hBesYO8!^!hgj&}>-ex< z5S8=3Fl$#x7MznDK8`IHvg6H6Vf(tR{0N@;$qew~=B4KvY|oL8bF6R@MH86qCzZh} zvF65BRN>TP&3PWcnx3ROqr{LR{%1|l z+el*O5UZ&p(0s;$M9ie3+?DQJPE**J9rQ2fn0kRQ_In&0+x)Zp+BjFtOJPkZdT2@N z1*EEifB{9No;w64tS3SefbF`QQ^*=hKT}l+H@>}T&i>wIDhrizvEUO2*?{eU>xSz7###oGa-&BD?`#dUh+1eIN?c}Ye36cJHSJ^ZRHF)KK3u7sczzV`y037&2JX(P36Gc*hd!r zD^8?wRb+UoAt91#X@K;Cssi3G@yiPZCw}6W9lv4kfmPSWQJYv^+=yIFr(F!8usT4M z)M7v`0~sBbO{RQSP4YxcRTNakAX9`gNQ01j#M5{xb`Uq1r z>R`;GDVLRdPGGMONqcnzzpMOkUJCO1;MY)(9aSLotRw_}&@Jc|?_wgI)s>L=(wpAH z!XTMk`QGF?|C?EFP*&tATb+sNZrLxWe(~yT+a;$qzU(ZkL9Na3_F`OcaXE)dm5fKa zTlq9#^D~K}rO-+%p@Uo>HoX8w{`aB;%H$@$Ves=@pizpcq-J<`D#U+$+54_aLX}OU z+Y_bsIdAIF=fMB25tErF8Y&^j4fZ27pmxV{SX>PW7@k_dE+x~lK;=d zwJkK%ZZmB$JSq1#6!+@umRw&Rh%$m-&LdQkW$YD60~2cp4`(hU@1_-MUNw7Sk-He* z<5V7NvIu_aF?dt6`*HS(ki2QAUfd3r;CS5xMjp_pPS}tLyfEXa*PiP1Uxp#ydw}%U zIgQE|P?O8X`QMVk3&@h+o3BphwB9M)2&Axm^zr8+F$MY}_IcCa3<~akjI)im^eSB9 zi-Jqy;zPb;ut06-o}rg|BBmT3e~0(4!wV`3;g0SQHx%(aC)eNVin>(#ev=B4#!zc+M{A7k0=o1Qet=bU{K|+kzz-Z3N^D$9++n*%k zG{=3{=em;ND{cIAy!{g91Sn_wlx3I7Yvasb1JV1&pg_1jNN>*OeNvk)-{6aIvkgAf zJ7B&Jpu9h40-KV(O$GfN9qNoJj(UPji;z~+YipN-+xb0n5AO@Z0$@o092Pi)`{oYM zDEUNM!YgT}@t29alW1585xaq2#v3J1gv6dwsIgcsoi|?pwN2J|@^#qzyIncL$=$^Z z?vCd^n6>{P5o?C-3Ro_5&>)zLw-~$)Uxk2f7AakF;vc%Mk6b zBf$%a4EU_C!kdU##y)$YFvaSOe>#04r0pg3*)%Am_SMr;sg4cC*^zcUil3}vcKm0i zd={QEb&oW+=GjY>5|;ALcjGy6Th_`8OABb;Y|T>*h4H9o;`&ZIz-H$k7J0EAI~b7s z8nZHcC-=9T1Cps$-s*mgQN1086Hu@CB+XH_Bxl%B@s2j?TZ?onWkizVIga9=akhQN zslCd?PkLMn5(akwUsy;a{FVcS=171P?HfK(s7GQ(vo*yU4IY$AHPB(q6hzAFj+iba zVcnWyuh6)WxvNf2B#R7d$-J!{RNnnWOh#c33w`KcK8aLUljIcM7#dJ@^Q$R}^Mf?qNf zg=>gxU5_yNM^hez+2MRGIu2YYQ8cP;#INsTS1W<#oXnL=z3bBes>0eC^^1`apMQ?? zh!wMIYC^_QS*zs|!f~2eTjXnNerirLp1ID2Kb6e#t4Bt>e1>~{4fUC$E^WOaqFf#X34_G!=JL1k9DXlY z0%~b*abX8<&RtEzuNv{;zsM_6Ri8-HfLth3^%#!2Y;63yizL|t5iq+?CfZm#z7@)sGw`wT@_UKnfP^D^S z33Xw@>)}{<43pV6UkwJEuN062pNJ{{B#!%x1C6x#7+yr)Wi<9ZAGLi9T*YvscdVzN zapU?Ub6S_9pPUK5T7QnTAy@WQEuuJr274mmEdk${OBYYUG;pyqHMVhQI^K_Rjr5FG z%d8b^WNOl$`Oa5bC&o^iQ2>emL`<^hM!D~ote1Wfvl5gG4cuve#li3aV-}jRQ1Ggv+Qm$v>_sZn4w!=9( zd!KpOF5Wi^<%EEx3U3hcb%KB=j$e-_ z_V2?q&MV&P4@={*dnZtZEWh(0dTMBH((v+59{Y92+__`+St;S2<~d{)AE^r(ogXLZ zN{;@)zf}kGe2*|?GVFJ77k81nXNXO0$Y%7%-5*_*YT;C#-F3hT)8(gDUZ09Hff(mJ zw5;_7>8BAm_09F1gP)Q{!vh29Gf!lR;9GE!raPhLr`!I}TX4~tsV$=BBrhb58!QZR zPjUIve~>tSJA!AjMs+&Eae@n22DX1Ti6z1th|_q^H}{^ydngxpK-1xf)zri-j{HM03D z4N2@T9vOI~%ddEsF-9o^ZVU+$m5>W?@fh`Whe9X6UyM5=qV|yxBr1{~0;ThJl>FKo z83ApI3S1Lg(2CA`Nv#vb=Cp)uM+k;{XJu#rF2|!WT~5^ra5PC z_0=zY6NvYSE@Fr)%dMEavnw0gwok+aad-%#;!g#ktc1)8^fH*xoi~bi1=M*FQqc4L z;8FqOhiRS<#Xt}^Y}DG(i9Vv9q84RlVVc< z*1x@toWv+kOH_+s#ho3o*$2=PCnQy0Uoc$`{}{gs^xxWV#I^k_K*|Xmdab8UJc#W4=sRc%42JPE3NqP z^S~HM<**T>=SlUt;1;NV{wbI~$*DOoE&ftEg9DT+JM&1n5iA4=t6-jm3LfEWL!LnrIwDkk z0ygPQS@z{0a(+k^A0&fafbipYA_m>z?e)H1Ghq_h*-({I4WmR_K}!4jvITrgGRHBH z?%tVhRBCOUicgJR9lv!X5v!FGn)v{4wWM|hbi|oVJ)}tgxL@$*mdSA}vjL#j2Uh9z~O2*96kTQ?}753`Q^Ys9r)J zK9Q!g(iEg&qIKByiM6@2%H4GdbT$_=Yrj`X=k2Rs%^Z(#?O9)DRV)iHmlj*N?qY6f-l-( zh_fDM8P0zDk^)Q@?i+h0pd85T*9+gBlQ6&m7tl(Y0L4eM)_1zJ%H3o&EP^26Hwfw_x-av`t?f{C|J@z-L(~3~GR5(c3Uu^I%-J?@ ztbdY$OZZkXSus;DFn{`WC*>=35j)9_wBOA>f5E^Qe=+6N57OKp1E<7-;_?-H{#!F> znjgif?rQ(J#GqF8ezqIE_*@!bcp9;WdkH!%4n`w zQ~t@oJ2_8cNj7DW-6S-Ypwt}u`w%dGi#Et2@s9e|TPj3CX!`(V=pQ7QdG6yE$JZm} zbaX#a_shgez-K+WODZj|U9ZHrNu8a5kVCH3_YXDG$F6g~${JK%!OeeHfxldqt3BJ^ zz=I#cAAKSutYrsTsc6|opHM`E`aI|~z1e#IcgmV<(2$qfUt5nZAK7=mmCwh9_R14w z#iqz<4aLZx)$N1C&W-}wD;2`&;dnqTknu#XH$&DK-ok6Ec(FJiYz7V;Y2x<*f(mxs zQ?I#@LD`z87!$<(DClSZY@Ads)>NJ>>9_AX2Aw)rUdB%&%25I}veu3d#etxCF;WV6 zS05RoalC3}=zlpNXQcKIuKlZ`1?OL+Rc%`oH-%ig+`Gq#MNOcO8V}CT4XQSWJ1k$OVc}iz$)4%lE*Jd1F&=m_SVsYDm7@2QMnq9f#VAKNjr6@%l>n3u3p$dD^JemE4aM5 zDk(%-Gro>vTS#F~#n_nmG!G(ovU%M+`Qo*Mt)~7#PDmpuM5fSS?J+)+m!uwJ-Q=A- z^^W;*F_W3^8Y?onPi2Lc04ZeCb&+$N{$PI<91_iySq(a>)f-uaX*G_Dk*mUuaGnIU za?pkVNpe03y2F9t6k!%Bu)Fy<5O3-a!KP{9$3%H{3u6QgC*gqp{BT%Fcqd-W7W$+9 zEIARsaxE0R+ZF+M&!LnJ>7o1?_y&)Fs~nhJ$y3H%f5{oL?jZq%-IE~De}g;$c&$!3 ze9~_loy-?;?3X`{S*|qjw#cK4QZr^&AE^qKP&D%S~xaIbzyAj#RP z2{W}X+U2kBwCqwI1jImzjvj8*9Q~@ujn1UAevUM?yjz8^pNw#N9s8mSL%}dO{~w*O z`E|dj7DtXhy7=54mFg|ox|K>_#aWEt;Rp3faEL4ssQ&$sjG$5yWuO$EM0;41Tt}Si zA_{w^hN0U+WN2|dN%}`8mWd|PuhjEm#Tdv;M6866A{J_k#&{Y+>{mG{(fI{>NFjMY z9Y>@n*WrE+lukaeyW8$G^L2_LW^LonND_xKW+(z4quiKgga~CZ?}PohvVxa${yqX) z98Og?qk{78dzE4r_FtW3egN_Hpp)+euf9ezr5kb-JzJcR0t)~SN<0Xnp`m+7Hvx<{ zdu;Ns)e3BWHYM1-b1LJrwTxQ{VzSMI1^qv0ojf<%Np<3fHay=%y9^HB?-t{JyjZDWvGs>}t-Tg<&8(JWn{LJo$ zzO1Pq+Ig*|j!RT*XaA zGLlS}W+G&)pBw>wq>x^D#Ou;~JkGPPId7M8bVKvcIbS+pFevFz&^PrZ4PyP(Np}b$ zh_nYzlR}2?trw!Ih!5*)h&^0lzl+fhp%SJh(+P|LMf|NWoQWJW5AU z>I%o~_T4!=1vt9g=45iB^T77T4jd4idZvEP5;)wHs&U;aQ-=BTR_le_3(2xIi1ldp zsj6I?#=kl#3rb6iY-o=16ukP~xjrv*|K1|k1Fr|04w|g{bmzW5h?3SI(OL7B4&ppj7^olHku%?`pOaH5kYjd_0+ ztJ@lk=Vgo#n`KsGq1uuueE%JFlyHJ4!%rjh+3aeM`dc&yU`Y@Of~fO7?PMy{S&)sD=}dh4uQC|@*g{5^f9*Fg+J zV$5U+Ezm;>LBJg$3x~eO{2~deBHwsc+3s-2Gf{|Xa$laa+J_VQp_Cwm=(&O)(V0WM zEEHn3N$BdEi7R);?T!VBm^jq}$WIS(i@!OauOquM86r!RF&7AxX_Wx=uij@pbe9T|8+a~;sLLP45p}L ziIoAKqlxnCkrEm6G%1#swTF+6;S^aofwDM)wH3nZy~y8fN)K?%`EwXNnJhZmwNarD zw1Foy&7G#tRPw7qFXxO0qkXWrydaJM@*uVv*G7pJ8R1gLB2} z*4M%Y{-{6#=hX!XC`5VL;hRK6FiMQucvNz*@a-aK%^dENmOvK!Y4c$C3JB6)OD~(g zNVce^N~sT-htgjsX_bk)q?qR#b(~uwEx*de1g5^>b-8r7zIRkF#%icR>n~Oj^L+8E zNO0onju}@q9OpHtYUU}JAgH7gLBIQ8#|!ni5=bmKPIFGhO2eWV=&^DLUx22s!|Mm< z15yj2re$5$ppjRY2lXczv!*=DEQ8mNNS7vFE<7{~Mt8}xq<*h7to}luvj?|m+2lB? z$!A5Ii2&qY{z^FBQuH+e+GwZg9;!~vC~6}QJ&432Xyb&$+=yY*XyFTxwOK9eqocO^ zNUTQ+O&xNPuRrcgka+9ixDd;7Ju5W03KyOj*aab;wE z%eiX7wgNv6V*R7Lku1&fFP|4S(^b0fJa!bDie*%URe!~*Tin2fT3UjH|6>S3RNo85 zC~p=-)*H1fUmyvE=Reu~9w9_J7tzTXDMOc?C0{NA^(+){SZixa*bcER9uIN62# zWzb#6ot+gM7zB_$NOFQm9@FV(T7v%NBL|cg6U9n^#?-4}9`;WSaI$I7K#(B5KTNLs zL;5drVX2>68J*j!PID`Mac?(kI70x^o5CLsPax7YHP&Z*+>IA*FIQ8c%9>Q~+YJOB zk^cV^@Sjgd?s+b^&l%OW{XJ8cgD<%|Y6X%>o>Afpcm7h55NbXUpD|ZpgRjlz*(LHL zwEdJWzxk>j`xnDac!&Z4aqj?YEhHdd&?mOZ)3L;7ePdqKkn(AVA9b{X>39T%)<3wY zS7v{Ch^^fQkbHbcFYboS9qY;9V4MT zqfx`N(UV>nT@6qp?187sj1M(~0>4tA*z)=apQ@Ln z>jBvoNJ}pIDuqp22J-E7k;ay)2CW^A395#QR7E<02G+ZC_5A1%T(oef zSw}E*Oz0@>QG^rKi4hye*RkQ^a`oJ{<0;}7Y7 zG1z($C^G43DwhlAhEB15{3!Sk6f^LJ83-zw&23|Ng>>4tc@0B=d#EePLz+W#U9eV` zHw6;@kDG!&&ix^!(R*S>4%Y5O{vXJsc(X8X;5_ z=fS09uRwEN{-~vuC!eZ&tc8%7vqKn*{}|J-;)U5GL)T9K2D3M;U-jkrfxYHHgg#wa zTA_B9k<*qGFE}f+w$?wZ;w-=rm8x7KA{IjA4qSSt73%&Lqs2q*hN-B&A?ldQULmRb#y?N z)IX#TxRmukV+6Xb#ps@)iQKFNU|FC%A{|Na?6;X}c7ShcyeIZ|zWth2KaS33G9!;T zm_zxGx51#V`72=?<-73BA&&`YHeB)9j}jjpnT@%+f|{Wy>QQm<%>BS2NMxhQ#Z0<0 z?C+5U)3H7tKre?W^fuV}Z1bV@?!~+t(U2Y$A+WSb7fAw{tupI$O>g!!+BBPhP zak-nO!0h(J0KaVgdBxdUqp;8NVj7g1*5^(5XO)vPn$WLW&F5V}E_{#ZKG#1~oP z{junxZq&xz=f3`qmc`MwNk`pZheD-85Kd_!NT~7xv=GBSzlb|iN_qE&+S+c@jFRP( zVRKT>dd8Q-fR-mhg2-QF0V(@|^zlTrh+f#TUmxAK)hRkevWJnW7602jZLWss8b>u> z_~uC8`0D0$Rkq{8uScYx2L!XjSwOx|C4~U@kKc&Y9z?Dl>Vhrm;$2+arc-?w6a1I- zuAAb$QR7YaS8t>p&C%-z#EzJHD2J^s&)GRE7Pe7!fC*B+7dN3oM8Mh^wmJ^T_ZMZM zr<)@K(aeUet>Zeq_PGEFg)qMa$S=;AtA_B8Gou(wxXC|zcaA#qv3}98dzp~pVdUCr zC}~f9yJzm3Qy5aI6SEJ@p`wU6KV{v4t5xT%x?RoYbvVNOSWT~_hPp;S`6eJBvPKT{X_Naqq)<2H z@~*)H#%bRZwrToZlR#J6cpTXYkbp7Px3QILY4#sN*Qxury%a(_+5IY4wZFIIchh@L zolb)w{k4OBvD=KR*^~v8OQFgLZRIrY$sKTvQ07!*8|?sFHv{0lCcYH*ofwyoba}7gu71)M>MOR6A*C!jUqLp3!G#d@mmi|D ziU;T(NWX<50&)Ie=}svSNcF%Qy%c0aYXfT`MtLtHy@8=dE}H-QL~$VV2Se~&VQk5k zcSzng`_khp*^fv!a9iY4iRw!Drl;|~ESU@;?R&f=R)UMw`JANxDMm@=KhlxG*6@ku z)D6{w5VeVBB+L#udTOB|um-CQGv$r9ErNgy1y;do-ZK5>4&0S;jpc>)bWJ}$DH7qw zm^oLzPphl7z~QA--Hm&3bJ@dw&r|$mmJ#&!GJ{##J7OhxD2EQ)%#2--fHJ4YPOoSw zbzjL42NVAEIhhnU(Zsik(PNTP$F3inn+J*Zm-N{WZM~7*86#C3-BkNv4VaX|#3Go^ zgIwWv4%@7)66O#jP-)AI0d?VDn>@>MzabYQu~ke5BGc%k6;qWbyj1f<1u)6g#2($4 z*kQ?|70~QpEbLyQ8yO7Kk~tV)_`ENNZ$f?`-LwMZf0V)+qW6sw0(OR(GQ!Y*+78oM zg9%XjS6&C>ee5$S4Wf|){Z9eiUNy`H-zi3!&U z4;(G`W|pf*$|35tr#>1R@@ih4Ybo|X9{Wp((#=gUB&tHG3|AY$TS+r2!7=`gDF_mY z-W#FsJ$Fz=XU3{1@~R(Zntq;bSoT#Daj3xVr7Ge9R`u!!*eIMzm-Cm&cPgo0-3TC4 zmL(Dhm)#|ae|qmRPn&}T^c<4h$^Di1wZ?KCG(VA3$yN{e_>Y0;uvC=nwO5x>E_NB^R zK4W1mc^J|mn0^;ZELfXyg$iluyjH9Uj*&)&n3Mf^nv8$j-sBn)1PP*q?!k;7C>?msk26ZXgNqwWQIaue zX&lMweHDU&wpb*Gce3}z$jOMuxVBq zF)cT?!#B@|d^V;*0YdqCZZprD4T#MGmOU@Gyv66&-6JJl`F0d)0#Y{RPMT7YP=V< z_bey7J;Z(f)qhW7@rjrqwDup$SRW|0aoVVN(cPXuB1W~tGoFh&z>IMQC}m^?neoFX zstlGiClYhM=SIvlAJB(|m}bJIJR-eX_?KOofwd{yJF!&rXm-rZ=yphtBe#L*mwZUS ze&#>Ekp^yg_IdS>eE%9M|BhL?Jjmx>#AzX)8ZhO|MKTP&HA?Jc39x-h_b(ycs{?Wp~u2+8n z69nl$(e#Dyl3pXk+oPotZM`EYW?oe zN>T>VoNG$7*mknB&w7u0=Amn9>VX12Q%N|NZY=F@{)|&HD!tR^VRyYYSR?C8{u#D4 zf_-X+fPhT>Ly+5LMb`br8TdHmq1##CKGSl4c3L@S!%g3yx}JIvQ1j25-4E+@+sxw4 zdFRX5ieK_)TfYh{FGM*}0r2zxB@|?a1a3>WR<0arv8?=g-z(NDoi_Xv5TcK4MqM2} zMbw6$eC=(>N)raXt3hv;(oLEvmfk-Nf{*swGoJ05XBuP)AEvsva5u@`j|EA4 z)!mJt4bOew06GNDgUhq_Du`uY5ggW2oG#hc59`2uv`RSZCtAjCp^X6qly|u8^zxfn z<<<7Ut@UG%6=hezofvQ^f1hkOi*mAA zlr;~W3)IIYxb-9FvdLkKVxkV8qDG_^vabXKWj^iZQqK=K!=u_s3jhUIv5|pT;v!>T>y<9K`D@ z%%XpY?b3MtYt>UemHpxiE|%&G>rjZS-3((&2okSX*-D=KDIOBzMM9)EUj#FRzyg#` zJN6OzmEqT0-hjGC6He=}Gi7!^BT2)lJryuDcp%>OBuo%l z2oJe{2huO~p}t|I?6TSB?xZ{?Qz^3eupJv*%ETQ8=3^1s#h?6unU$S(NG207tIYq##Y`Xvq);#%S6nLNtt z+I&G6Ajrr}@V1Xqq4nARM#Aj0K5R(VV0|gHq$4@O#2bkbcp$d)BuEf0R3Kgd*|Wel zGH1op>md6B#!GIZ@s)y0l@lInLm$+F{(0rwOAULfNk*S2ZyJTN?`^(Ik4O)? zOdMG7IW%+q6<4U+%#co?O3@eSSa#ubKX7LwTbBy@p}(Z({Aem;_4ryfcS>bU|5>J~ z^wgQ;2m|K_GR59A#1RUhP!avwDgCmQv%;%E+dWN^I4UR$nGxkv5a#e^8hlh==0o(% z!Wh=lEXspDdOaTl`*7ptoT0QVME0c87a_%cLG)o)uARpl{ssX;}E;oJXXWMHDFiSd{8Y zI}Wq#JI|{NXHYqcVXyZfGwGujnv)6pC!nOZ5#{v96eS82=xZe$Qm3#|dMXUW(}+Mc zim@m>8#`bclSkhDMsI~7hGJ7pTbmQ=)N^=vQ(ghZ$r^7t2VLmP#O066(MW%=}LWbQxxD-?5;imjx>5XWV(M`W=c#qdc zWja*~0vMH&ksgtbA;GbN?zZ}JNu%Q!QIn!dz&x`;Fxv&A4?i-ouvO-h;4}XOVeZD8QlLq@{%ioQfbtKCtVzaH7DXVoLhY zMkPp7_{@7gi%d|G#&wF%*du{Y*zl4~45y0V)UDdN$;?}E z#iR`{j3j2CiBYSBrmFN6RghB7?N;3;e?|2__riJ%U-rG^3Qoo(E>AJ9#Yf2_E!ptJ zoer-SDs>urCQuRMc&5KMS<7r$aYi`d7#e9rF!92=9i8x~H3A+F}p(I zL?K9C``YK4%44UrQNw+EIS>B&=)3g2&*Dbns6YNk@^B;sK*00z31eNII2$(_=jqa^ z8|}LM3bu>GvNT+*~XTUUy9l{!TWZ3ryULy9!IP z0YUm}2OETA9|N?57lk^-<2B-3Y4W{WPhxA=zZ9uR4P81j1ATkgip8f0Bw{Z_=FU?W z+;)O@GQXbkPVUgk3?g=)jLjAUm{KvlL&4q92@eUfrHzS6hR4*Y-ECXs%FP5nxi?TL zlRc1L^-#$0hjhyFV$rujpKkO>CalnU-9*B8bbSchDrWwT8z8BWXB;SOMOYN?*QvQa+M!IZ#^UlYP3? zWvv3|=KPn!S-g#M>ld8lZ`$28e%vf80=1oFaeAtQ1@;WiRx=aVxS?G>gu&P;4WYKno3hE53MXEsTve)Zo;Bf!gdhojWP7|Y+q@=R zJMUY&Lh*}B?Ui#)dxO!c3%C~}QZ= z8bviJirWP&m?kfecsS>6wb?SG!C9{YN*0#tKLKA5!9#&(9r=5{8T`6OocL_P5cL+sx!ed;GY^){ZkKGlf;uUO^J!Qz^FlVi zPt+**#}|U|2cTr^UrUdIrf=VfEE^j|r{9iDy z00{%ZOCNO2DHzBv#NW8cp}EPSW^%he48GigkzTRJkH_=_)^b3|igJ-&I!93J+Q_6mh2zZNOVzFbUG5uROj)~9sV&*}N;dPq-Ae>*PFLI5^*f~lhxt`@>q(->As zTpn)DLx=Z>0`j^$1n}`uyj@BF=`vA6U5Tfyos6SXWY{ThBLh^)+6$>6VOG(#)JT1_ z4?&P1vV{Jf!o}H|1^cgVGL(cVIukY5wvKa}V*VFpjk}#h5tv<DC7s*X*CIWYzF}bAG5inIP)`iFV!@U1CQkawVa5SDdcdn%KG=mQ*C0Jk|Ll&|+ z0+zr7wuR!7fQ~YpLiF=kwnQu!ddS5eJ%o8#X?ijK?V~U~0g&=gL9w7}ho?aLPn;u} zmGn@vk6)`7+m?=6Bda+%ZZ5ysC%`go>`~7gM8`Ths+M~4 zD8E;eJf}AN2ihIATWVcwnH7sI9%SKG;(r1P9(jMTv3(}UYU=kDEAoKQ!nfubT`I}> z;?OhasID_$(<0@Z9WvBO5w$x@fcjlo-Dx}c6~0UfS^@)&B>K)=v=tDLIa?0aco${7 zcPlJw7lOaXBriq?o&t;I>-KQ4>yW`iAjiQ}kiv%{C+Hi2u3J%BlkvxyU7=_m7@0`9 zJ^&z{)MiK5QC;KwKlE_GB_Vd z>;~BrFde85IKap~ znbf^0(&Bo)V3FD{jw`)qTd}+v+JbaWm9|6{nB1|zQr48`z9)S3$uhO6+z2}uy)42e z8g6+qeX>Qg`fHOEgAmBj`wW%g;UjlGihaP_Vrw|Tv~TkLR4guq(^OS&`11SGL#5_l z((@Q19qA}*K2TN=L=^CXIcXy{vO$=j430Z&zFSe02inACYkU0Q-!tps%;j>vXfir9 zaW-egz|?!mDU)(OCfVruL`?s&bWW993asZj&ID9QjEV6eLLACW$LE$vB^N236?aXk?kNGbD;#-A3_+g}uOfCfs5SXBHU>98uP zX?B6o%utxCD8q6$l5OZG6klV{R0rM+5N_;;0tpe$pM|QOZwm&-8kAV?aIP)_;(0oJ^Yw*x=oKrm*UL#sMo>Y zz%3DNA7Q}_;@=N(@%l{JuN8hW=4J6J9kgKnY+Bwn@Ii-Yh>-N>$8z)+d_>Ph^F!Y# zs!&E(tsV9|tV94cH0@#rV59ug@5CvJg=w}lL%*M8I(>mqp5>5^hO<_Y1_X4AT#p(* z(gt3J*)#kovcixrh%TyX@_gLA!5%g5S@m;mfU>SMtYELKQuS+FcB%9ISbEcNRbeTk?8) z>4FsLvqiP&BqRa1(8<&W-=3I?-u}%s-=UIuz`e}ASF5?8eEseeQsm8+O8VWh&~4oB z$snrsH*wJ;l>^2gLx50lm;?RpgY)1yJA*+p`2HJx`v<`^mLqPUR)tJ(Y36q3!(I#8 zNLS!Sct41S|Hzxp6TklWNlcNb{BeCOzqkTcVWIl3;4{1tnPlwAXg_KycV+|{D5?Fm z_{%xMM`m>)>;%)JgBGR3hLNwF%)n1$m6n%S+hX9^X~v~-ytfHFQq|*u%>9|Ox0PWq z7=|zTYdCF%p0L(;wzbbT+cymWU;GT}2iUxV5i+ zIF3Y1LR#~GN;9=}Z(G*yr9X_pS7G;{bm&dv&8Eu(AvIchxV;-Z*`UJ6NYX^bXurg( zuy^fuagg>lf0fQv(7T1lGx|fQGskIo%r#XXRBSvKwb~%6TWFPAAIz>tg#m@*~=&8UcRn``Oz$| zs?_cEsvi9Pr$X*OR~<#nSf7Xkw@aDYxdB62^ua)x_kBeHDjPXO zvwc{`{xcG)6W1{h27;hu7*vqGl16vhTt=N3oKA*6_G#WnXlx zCL%NO;GRy*_n)2%W|L-hp z&)$)tR^{6}B|Yy4#?7Y?T4%GG$_TS8KT5O}gWCz+KCY3x#l68aH~8IEG+(f)@Z@z2 zel{OVL(OL`X6P`EeR{+u6JFw#vM?v&7jSNRz=bsbsxy*M3PkqGfvkBp42R zrtb>qwpF$B0dL^g2rN=#ufKd4Ch>Mh>}D!-jRivjQTIMX^a4FFFc$Pe2!>NNT>`i_ zR1);iE&uoE4@xo$j`5pFXd($3ZV~M-s8Z-Uz&{f>QQ))VKB>RX0_w6 zIxn~&p|G5Qapds$T7$9O49xEzYbsoL3?=>y;c)T~svGh_?_4TS`Ja;vmiB_kgo$L< zs9i+{U)Hq0qGlxacG*0_zr>8KIh}$T7)v#W0xaB&M)&=M$ce(7DR1%#MT9~uRef1y zJLo0s#P7_-G6@>6g`+%W3sgi#iLacX-X$#y6DDNNaQ)a|MCkA{W0uY(tpjWBFX^b9 zEKnPNHjnAB&ge)v9Tqhf*Cf{Fp||4Uk2%9vjRpYHS?CxpsQXaqGLMrwIekfctkS8U zJLm4!tpuUAj1Pgs=@J~5>6$rhJKm+aC*KLkJ#0)&(QSmVMarY2=s4ok#UP}gIX)=; zGhR7j$Dx$ePEDVsvIkxjsl*o`xb(P^##sI`OAt$0Y>t?h-=4jh75R%!#v{_P9pB?z zvGR&G4yDolq-N7;CG#mWU7tthV`;?mlzau*MgB8Gge=|i;Y|q!F)ZL`c(dtRiczT+ zEoRUPk*_DgIn)>vfymI;X>~so6G|5n1q!Lh_QF|^LtElE!K1-D@BT#E#-#*ECqD>| zF;oDJ?2?&UXMJ-ZyFWCcDmv`Kml=y!%?{l71-9_>CYm$!=XB3aBK}BCVWK+qe#_>f z32r&@ygn~6j=RhU(yavKzrRW_=3n$YSKG!ad`hapw1~2qt0edx>vZ^%;RO(6yqm_U z$92OZRa|%a?m_X*kbn4Q<$_1eY59ZbM|8g}0FoK@8_Im_mHj|Ay%fsM zBOt(>46VO3`jMWBXr!R{5$Tf%>p=$GCqa%$_}+4;Y~Rw0`b2dmQ%(-mlW;e5>iHig z2$?-&SFTux;OE_y0_%0|n|vO8Bwfdg8Do?+%DL|E`+>VMx@L$g7~Ppp%i^3W{P}a{ ze0biMUfaBd?qPAk|6aik7|6z-aqeolr)XZ)g<>w#a@_NxJqwTYEzVic6VE-|%?q%B zHnCJ{Xfn|61~fyx7@k*J``BRxzbX#zP%+6M{O&>U{n0!cRl!~Z?Kj?|gM9WL^V_053I}z_x5ba&rXdUvzR`=!$%IuuWti&ai?)s0I zKM%Yc z-3Z0}vflQ!xo~Y`>bXnQy-ki}?{I>|%~W`a)!g^i@dwh)DwWySroZw@3-b1&rV>Q) z@ATKU-&e>i^t4`s+WCOsJa)~O9R0NB_eyj3_cMkyVn2l7@{6;t;HQ7yT9KO8fv@Ld zrl%JbHheYaYb8Ns*w*%*u6@`;-%G0Kxel{8NtefGFzE*5Ni?eaZcmV{s-2!2q_67~ zs-ux5`elYLDpr`Q&u=~=y>wD4>sjC@Xf(MlH!6zEC3p&p_5!hZy%AX5<4(@zG9ZDC zHN1+!sGx<{;@j_3*Ws|IVT;t;8W%DR=p62~ubRlHvP%HU?HYcOUO2T@xLvdOIo_ONQj{In~qW0Hv3pc<^@t zKS8@P8GE+=;j>c3T;!!Ezx{4zqr@Z9?}kYB2PaJUl$E3kJH0y>2Th79SAMC}EmV5L z=pluYgMa29_mTpw9`L2q*j}>5Llo48Sag__9ll(v!Sqg%a2Ubs6u2&#?P*12-sgA^ zq}iURgUJNV3%Z#R8Y}yiMwVdRUX5&Zz(P6ha;X}PaM)Sx1Y$xo6OARk&=*>Hhp`2- zH&A`w#Q4Fiiy+5Kjek4nE>+DrhoK}F+cki%QEh@3><>A;sHJX&)U^Ed>5l8J^Y2=M zvI2-_S>ar)5(hIC7|@-US3`_1glI@Cg8@hjR=wABKr|_#d`h))5+#yoH?64BuR3je zcvoL@1gK;CPmn;tu1~>KKqzfRrYM0y1Dz-8vO1T}X4wvtjE5=%mO$uQqU3eayQcNBdvud=L{s@) z#UGTV%)2BOx$e*R4yY)>Z6jwIBdwiAUVl+JTpr>s3}vI8)b$UUkB=HD_=OWGW=IO= zJS&VF5p|~Af!0q0+Ku?tZyuQ=ha5xUW0cWsg=S@sF)*Zmbg)9CMXACs%34=2_$|q) zt>PjMXQ`NfvN;tqmsO@u#R5b|vh6RnW?L1C^9$t<0!(3;2a{h@S;=qj%@Ge-;-HFf zfOHi>)-6t0x{30#{eD|-4 zVvF1wI%=eqUpaL;0Pe*M2J#44>G&jcW{ScHiHXB!7HPgYE$EePHV>pjuU`B--rQNV zjaQs&yhFXi+RqT!`jC)$T`uovo9L4cN$~zlx+`ZB>>B0IS6zFRpQSDeb7Yg?yj|4p zH&55MIB!A@D1a7yi;4stc65dWRo_o!LR|27o({~HK;JD;W;r=2BKVn|{y{SE&n6MI{9juaF5jAoM3H#YYL_*MUW!tmY{ z8T}3_!r`kz8>NkMOu|2gX<-YK*7R{wZ&ZWoB!FvznB<<}$oGF5xBH1#X~!rB?fA_^ z)SZWs!EPyU7GV+C*1$X|`!*0|l<_gswgmPhyXF3nh;FCn83ltYqhvKfUez|(LcyF# zeOTw4u+ByS+1RAKjE~M(FPeX#Qk-s+S#G^jfb28>lK%Q7Yv(}1lc2eh`b}EHA@_@rlL{Y0$`B5ba4R2RER(*L%XdLAlv(6Q3XceU zLf*V=Ky>}dp>6A@_!R0}2FQVHM%q=ayP0EyXQlwITbNMNvM%~>45e+O@||YZD@U4R zux)=y_ZimzuyIZvlv~2%54}B_AN@YJLd5)CbfcN9suHV(0}x#5D`PKgTp#53=UU=E z4MlwygK>ky-O4?G=r7bNHwVNyV8XvUA6b|k%LM3$Z@kM<%sD+{oYA#Oa}!DLca>h6 z?1hm2q5|Ce;O|iWEpc`fsYl!S{tp$)b6(N4tBoH`p+$j+xc@$3u}Z8;p`WO@UM|bV z@N}{NnP~UTBhueV3sZg_dZQ@X>~el2N>2ZK%IXjw*m>!vtvf__T-Sg}|7)Nc-AYKd zGLc=wEvl~T{{5o2{wG**3yLET97n9Pnf`qsq@cff4z`#vSg$3wqb3#WVg%>O9-&3eO&*m1CF{|r8V6DdY=-RKy<|GwH+U4Z{1F&0X=Pf?b9Xn2PLNU*>+|BQ!FiGY1rsRY zEo2!0aWB`x&b8L>eShd6887QIe&^vo&+-Q-eOy7|2{)^dr&RqjS&M7XI*l2WtN#8G z>3X#cC9ILB-?ufGjjSv(-xwCA-*UdUU|tl?jEL z7wwks5-nEp_q_Or>a7vClYz58>~qNj)#xVt@2HBrTDOjJC&pG(4sylZV>q~8;x3Dl zfY3t`o>cP&kJOVEcbu59ap)W)4z_29kgm)KVmvt;GbjX;9)~P#<-y#lrCAJx&BY|# zBKP}gREQ=hLK5cV_&XT6JiSuU`11yH_;yus)u} zATsw&9~+g=gUO%Ys9zqQ+fz}JdB}-|1=l%3o$Ki$JrNAWv9ra@w0m+gQ7P`tdOO(AQdtQtNXA zVa`-ZYcySCITOz7jyq3Z_~p7wwi|YmfXFl&(;MoR$3~?x4+qTqqB2S^hT54X)a*R_ z$D`QHKC=BsNT5Cr@LT`7mmBmZ)m~#-L>kr;EnGXvHo42(Q(&clEBx;hQYhlRFIL;D zrW}`=ucHM#hgfGnKO$Wt?}KiO?<&rp%t=k{rSwIBmJpqiBiYh7!T*}X-f(u#rYril8>GJN{Zj_`^WKe0kKNZ7ijrh z9rDtvfbMeM5R`rQcwkM8_yGsFMJIX=+#Fn5=5>l@dx<8z@kHw*8RTRkFOH~v3RdkB*9jJ4*%UdM8lqpotP@sMV=zi9%>uaq1g63da6 zLPQWkQwl^%gWniJgZ!0+O1B=74lgLLtv~L*EwJ8GaD|mgh)eq$Ry5|<^>{bpc^0O< z$|DEbsQKg0WiJ*)=ljWZ37W@!IBF`j40#e!!^*a0+~*ZDa3s<;`g>y5Dqf~5d_0Qv z<+IASc;<8{+p|ki3JCc;eD;8>%+eV3WyD(Q@cAm+OrqSqT#rC8_iaw`#7_3Z=);QV zVA6+jmv0TmD?X1OVDn`4nvof#AJVTWT_Iq$q12j*yUjn44k9Z3be%&l{Btzk`(=jJ z{qdEVk&^acNK?W)-Uy6RdLVP@fOCfiaU)(absjlp|Ar{-jd91u4${@h@mRG+QA95Q z$%y*LfVcJ(6BG5E@rinPfLtCGIC*my)b1r+5MB4|9@1Ti?P z)Rc%2*-e#LD1$o+|4rm3`80isN2GUW&5k|W==(;hvt!BE6GQp~XNePkOtFY=yvc^k zd<9siTV~ahNpmd!+2@5a#7Ov}l5OVGe4jH-eyoOy=^6nK1QkFEHBj~WDCCo+dWH=W z1oPFQUlq&3-gz1`94e1(XF|OJTS&DuK)*HKS^r$d={~V`H^DwU$kBn&NQb+rjLxS9 z$NqtIylj83eeb3pOl}0cPvWQ&bkt5=+|b2wL>)g~&X38kfym_cfA;Arw5eZAEv6*) zn1tm$U7boCHr~3K8s`)eEV~6DL51gWIdOXV&!|njI4%G464js4ruj(*Y%+SqVjxcg zr1%=7`Qag-V@9@h%*Vc3@+XPvXZq>;Aq$q~{r`0@a^9KwSUK{IDh5a8;L<-TJK`|Q=kVowg|+5F1RDV3dcl8T>9kg z@9FqdC3k=BoQ?}8CBVIOT*~}U^#keH*5?E6UCAJ52W(|taD#C&LafApX9lXy-Tqcy9h0+ zB@_Ag2`;3M#l~SsFQe76uF8T?R+X65xE_(-e4H^D`c1*IS&V%fsY_B@bCQN(=)Q(p6qNVE?pa|Nl;5p6+orJC)9E9{{F(7Q}naL0XXJ1sRKAP z_*tK2@QEduqI*xlHV8yHjLo-z=fk!^x}b2?q+d>}Z^TG5z)Fypt`I zqLr7+TMXOD0nAJxZ-^Sp4XyjgT5~BVk23JGfpjrOMIMpm=X+0;!@WvP1g?^wdA&eV zICuss*dxT!RVoIQj-&!x1TWsCZ&O9ki6nl7GhSa~yHW4IpDm`@ja`O51tA?f$|h9RX(*9Z-36jgamB=8mMiero6%Q|wp@WZZ)YVOwlvj;Yi8=6*XDc`H|zDDv$rrIXL zj-dIlFX~-{28LB~R3v*N82dju*o)9ma4)9HOitot4<769m2o7*b+uPgosvuB+@A^u zCQ{~xF0RBrIl|9yvPNbun8fgb6Sc9$vhEqKVXEB8#~1*n!D@x6+e#4=Y>BD>(_2_K zeI5migAXq3NasPxztC`VA*5$iJ-8Rxxh`oP^})m?8VeHTUzfMCrZ(Y#Pv2DqBZG{E z!6)*B@JZ4HTRw^$(>h#*WOqU3k4W#nH*7k_oSJGAPE3^Jmkc)Ntd3%r72j(k#Up<6 zqX&|j^^b+oLbtI{@^L=metpG0`#(FMU#l9nB|X|-m~KLl(Xkaa=Q|6X|siE+^KWuTH#cF z?^p2!Y@w*FLE+3{=FT<^>Q^dW3`-GO8;wbml(t_2VobX)mmR@s`6~p3P*V-nhV3Xm zb(!gg8UGvCAp8KP;vi)-yumf(1p2SQjiaxWuIqr1wP1_wxzXr0rT@So@&w6AxgE=Pi02 zy}Tv&KdA-%`Yf>nzD!G-(bqXvn(g6Tt`trNaov^rS+g@x!;oy$e@OpwpJnm_tnV7Er@y3I2u69BzK(?R3VInE7}#5%%@{03ht!vq z{T#)((oGWx2cd!JOj8aWe-A9Ig;DikAH5xO)bn_vBeNkgp2< zZ-{$Qnk#rD(jD4jbwv60ds1>@soWSK_s5=URjL2e_bOXrzs_D2Eq zY9QSK=ak^%)<-%i2}jhQNwv?+WI}MiebW6}|Dnf)y=)*FnA<_P6!~!SPV?6G?}``8 z_@d}a{qfpOUA9OHKu?4lu~5CiIsk2dgcQm^NXNAvp9TfiioWspQc-`Kve{dL4uk<- zmWupKdLLi*naezU=~RQLWfr&FHVykt;Um&N4Wz+$n7#TYGZ*2Y%dq*uo#aqaYJM2R zPCXqbkz;}g{+)kFUw~04Vz=4FHA$yl;9okTPnbh|7c6ZyZyf+NDxK92RE>F-=g`W< z=pikBdoA+B#Vtijw}E2ByovUczfW+4>Su7i7~&xTu3m2#ZBGGA#t4gQ#f2mCAI(0D zPmI9Ibm2p!Igml-)hjfQ` zspoaEa?V}KrT!+uYLZSjC+^AsrBxmOMrRTlKiJ`Q$C`F=uUlFp+eyGByt#WsdJe-W z_klA*)H8yF$Y#;IgXvE@D9}GBpk=XEaxd#mlEI|^HBi?+9*9(Q*=AnJalN|n60m)W z%dPa*ISl4P zfI}N09ds{kdMUJ`aeI*~o@GQrqa#+SxIwI^-aD{?UQ`MN@Nn!41*k92UOU+RV>RuHLS8T_dROMtGh1>}*aVpS!hKs2xIn$itimf)u1 z@^NeR5MI~{i7}2R`#CqSX{A8H^5rikk4ic%0|3*ON#HnZ^2PbGvv+eNdM{xBZYW)B zULKQ({g!Mcq3x0>Mrg4U$Xu$-KmtPp2fXq~ z`;kP7rMQvf*Yu93rc+4#DN`Pi-t9qJhI@8x(+Q2?efe&gi1svNN z(IX49I1Q&qCARCmxlQ~Kw384qGO>fc1}nL-N4=tkx%HMROcaDiWAI_FX0KpAz~~ zU<+5|HJO)Cvwo5e=;S)rP;iOh7-1ROu_C5pd_s9z!+HC??_4U;Xti z@P ziv1b%M>{Vxli{x<>A$+)^Q_cA^685>2`jISN$ko#OGY554Zgk}Q%G4%?~ufLTQ4!I zkyipDWBmw6`6m`@+Z3T@xfKCOj(sD@*{?6R^Tka5V#m_~Twwl86q!Ave=hi9H2qSd zcqlKQe&!EP1M(>~8q$qS{v2d;)HJ~SYs)L#Yo6KloAC)^wN^ZSMpOMN^?2}*yO)yE z;e$^{A^}h-*I$YiSGv+BCiw3 z!x7e_n;G-CEqiR=xdsaz0%xQ!u?wCFUy{^gG75_+9EJ*+Zq}_|&2mr?m~Z0U-Sb=K zLrBjvesCV2ul6D9BJ&wXv>fA%4BGF_7Dh3A4+b(<6MGsr$yL+F zR8V-GZx!o&5xHwfmksq0Cm5BN`ysTl;UE2hZ;tdj<`pFz>$Dr4VB9rIl^OP)MC(_$I{8>n3L$E4tw?D*j> zt-SC13&WL7EMk1p=1sZHEqvKdTmi6U|4cJsd02~NQltp*7j#?w!!b>FZ(I?c9Nup3 zum`S4GQH{pk$azkL>_d1^9EOQFv0JIP^ywokb>ATG945qPn+&Q{#r}t z1Ak}{e(ysiup}RTVa2)JZ-)Vf2;!~};dvWG`j3?UBXBw^uXGOc=@+Mcm)TO*#Nk5{ zwwhT>sS`Ov*!&X$@QeNqWy^UR6dzs1A=b|Q>Daf!A4x}_5RPa?D>Z8qP)HSx$O7Ia z7P!}VD>x001gd0xIUGJhzCU6U>NG9~r9~_n4e0wq4&wtte@eSnFnGhHY&yr}(EgWY z{gPH!4)aiHl*zW~;D5vf3LXK!_AlvWwd6Xr9>^X7WW-ww6@0U!UTnZ7|8WZ2cg9f5 z=B$FCQ*TbGB?mKH6q;+ZN2I_0%Ar{IJR)@6uWnS)tj4Ndhu3cP9cjVKyHDutwPdW| z7yTt2MSxznu!C!H4zGa{jcA-zPO!i5Bh2Yf%$TWMLr?IUST;{At1^VCl~rN0AL?*E zr9%x>`9-Vg9_|D__w7{XQ@}u)(NYhWr}c%j<5d&54k9WV3G;c$W6)K2{CFj$>|>=*^y_2SeH6;=+nK}4|1*T5 zpXgYLUO9agw#SA~jxr7OQ}lJu!&sUntZmPD5{)w8a43d=knbFy@jIaYg`KS}Ci#EF z1d`x>Af4?>caQI@S>!K*G?tjm>dN9#6@c`|8M0q2!-VD+MwX~R$>fr1yFDZOv6_!a zN9}Z;G=UqK`ryY58gD7EGwr_KM-}>r*uc--Kbm@={@`Ao{EF6Ld#ix5qY$Pjq9wIc zI=pm{HXW#!H%W8DuEhp~(^=AX<;`cUC&^(2jHBT@mDafqe!Nx}5yjX|N)<}pp#}_0 z?JQYR^XHW#2&RPLn_X@At%YGjNDuHz* z0(JDgUGM41ho+!W!tzfTceJ(P^6qgn=fsiX?M0nGe^{)wH<9J9|TpWOiJ6OPA@Sr>1Ke4cOVt8y( zFNDvf>cpA4&c;;?%HKj?#a6S41vi5q{Ht_x%;%(7k<;iS>TL`c^b-lo!*zVL90sg(#W!GK72WS36+f#h_hsHmjIp z0|NyK(xfjAl#`mDF)CA2%=1aw^n>Mcu+pFE{Qhz!!Xc)|wQ_*sxF;AnfUz{;R8j_j#JYB_x8mtu#yr4 z{t1M|ab7J_ayFw>aw_;$KmKaX;`IUc=ov82SS$)Vdm#9yhsYk?SPOqXd@=ayN6zYT zJNME&jSrcuI~WrPl=u*PNj~MY{F0M+x_Biv-JBj+|5jOL8;HH+u|Ng`__I)l)KWE_ z%;&-iSP}*{|MuIOYX0$v^n3l_<3D$&>k6fHtD+(71f=WQMY0Jq47-fYMoSr+kb7tT zv9JIeVJM{8O66;=ZxoIG$Svgbr0ih6qRNg$R&zQcerd{DAWRL1hNua) zFya-%^mwfWk$izVzc6jp=(uK+0<+6hZokOD^eE{W%YzVPS)y6pZo@tg=EQ5rh7r#k zVLGE#O-hNkoSS(mv`)?f7uet|XV%SzyTFOCbH*o4xsWlhFCx67mvMhn=KEnMb@V?% z`k&ZKgrGo}E}Hgh7Z+}$*%T=Z*Sng)|5duu*6FI>LM876ceyc7IekAzJIk|2q+6gx z(&(QXHID|gPS#t$kdrb`QHoqk(ZRbq;`lW8+Vs&_{$?yBqiq=RZEZkzRr%vqTywcM z)i^=6Vhd5y2(4)RHHb|0-Lt@x$axZT`4d@8ngc#!%R{AJKatwHuP%0Z(-~`ktfH4? zXR3~5jshbUwk^N#em=t$#rnPHBwV`Scyu!MV-ZYxz^qJhS8bB+0TKExv_z#b(o|q- z=#NSDpi(-Cb0y&nFs8qw&ry=Rf6}%<_}39zqZprqPF{N=vA9|+QS(>o!N80eI3Nax z+$`1ioM?@E)2V5O<2em|o7QkI(@q4Eb!cUh94SyA`JW*D59#t#i_|Lgkz=a{8G?%^ zjj!U8Ie^2s$K}_|@lvmtGBf|YN_tXCxjJt82Lia=nb4o1ef>7QG61~- zbc_tD`F0ikDoRQGhWF1m--97l(AwvX)C&y;6S8z^c<`2VHu#OI6t6w~#g&zpjC>fC zIulDZ3!?>LMXfls8WWEsm~;>5pdO0a--qfQu+zcw#yYYnZkCKiAnZLUcYbTOoXZEL zqi#rjZR{Uk8sJze*1ciQpXU&?Bgri#R0!o9d@;fbL?&?JURL3UMoG!*hgBG$r(@Y; zQ<$a|2+4XU+}5B};z?@h8`w9!59@fB~RJ5!?-C5;BE_wj5 zOFV=DoKSGvz=$SW=ncMa6q^9d#i}#D^0g!6-tm7_rS_9)>L`F0A+K>m z{Gt~74A|;l2)Uf|4r*u@_{11 zSJ6p`vI9rH=0X88u(dx%5nmO6IsZooo3yG&+YT)W?frxCLCHgG_BA8xg~}e69Bj8_ z6R@Sp!0Kf#$n2d_>73EPX+UPK>FZD|$Dr1Fc#MJ*{w~6L zj$Jz+(kXrT{V#Dq(%fUEL)%hs89|SaQcfJ+X+hbwp>?J{-jRO8l#emL*1jdiu-t8I zgIl_Li~n`GaKym}i(*Z7BFf@HRnIJdqI0sp@aa~`sxHEF7+$9|QAcs}y*&)W` z=>d0jB($tFXS`&jBgCTO#t%3)(VQx9E~ZVaJzUMJN#Y02r9ljK%=`D$Ul77T^KbJG zT3EAB7z$Ne1D@XgLYdf(`vfU@mxhm%2vBB7JcQ`HERHX0NABCohQ!Sw% zECkSu*n`q(cU7PUM?{!>1YW$gedchu`P4%uvVrG>MM#5@|?4&sgC}Xw%gz`gAZ0{YH3$=_u$gbYV zErmVNt00Nbs0*XUXdYyUhJ1UU@GzEDs*jN{g06TN9ivRmv-9pOUce` zo+DmJ5C~^udI9^*$4VD;tobqBmlKkCzt4mm{XJ9wT~PXwdyy?`BJyw68ymQ}yVLoq zp|GzvntDfG?{n>-ah2aI_qgwQnxF6669XPJSj|AXB09{9h97)ceuL;Tbtfvnk~3@Y zf$bk0diNrs^)KYaai6aUrqID5BqdLuSWpNoA(Bp?BLlV0Fu!%lT_w1*b8C4thiQ{xX%k9?D5@XLa%2PLuE;KFV(MTD?jhuw;~nEUO?sUGrYDlr!xYJkq{Qjl2PC zAjYyk$0O217x2j76F7o%{E2KYEsT6Bstfq;Yp<GqE?+9H`RZ;C(Ca0c1Ul*f%z4j*}= zQn+m?sd;&^#62Y~mR3eD#k{&k3m+9v4z6Ggjlp@WXVRBG`DvozK=~ zwZl}_^!!@m9z@0~e;|jlc026z{BzpQUj5n($!~CQar3??@!-X!7_* zZ)^wU5MEpWs#k}9_C&E!Uj>uSzZ3L?d^QJT zGt77~BsKo@HUX(~swG;@yZ%qExP8aoBagRAY3TemfNJ_Vth9Ffq_qBPmIB2*;+40d z>3pdJYq-E+xxO9uPmLV9-E5}Z)>5lM;;d!QRq|BaP?irL-+1pfg0~L;1=Ihy7i{sZ zb4f2)SxIwKw-=@gd*~B~6aVX8DC@C%%VBRuqWTTeFm|?fehl>k9SQ#>J5x)5Sfa&I+jp<9kC>%5TEC>KUYGdA!3ZH)iv?xyI+3H2*UveE|S4{-+$;l z-g$kjc3x2!l`UVirW#Q9aYI}JM>+-z1x)&NJk-4a99q+ht)iO3wu%YwN}czm4s6w5 zybY;)T}Y-LksfbBN+hbIB)_75Duw1YsmNJ1iL+t9=riLq>y9GsU13N)516Svn}U?$s<2nOfmK59%XT3ty+NdGhZV!gFr zlMh1&srl^j=7`)>L)UE5{`u$KBBbZT?+&zc=f!VNPRVTGm$6O>2`L{)XTp4seDD+n z*+1hj71}9Cr(UN|@tqV5%X{W$r29|?L%=D`&2_ktV5l($PAmoX`}%|huAi=6!qy08 zD^I%6PgO!m>7|(p_bzd?F9C014%U9WKR)Gynth(UB9{m1c zqcCQARf^VIPDF2hl(eZBrJR5=DW!o2 zr9=03ma=YXmV%PfQv|In19zNDSIq_|LzF9i`yamg9t*h>ix zt3Co$g*cq6OwinCjQ!4wgcV~*yv{Sr_oM-?^O9ij^ttphB%TiHO(OiaPXg-?0nOL3 zGjt6M=C{PZZM|4#?R0@;&3O}u;{zM0rkN}&mHeiDfJu9b%PHp!9;XxtKmFgK4E^Nu zP@=D2e}*_BD2t7Mw0WW|_??C$Y(&)uW-&qoV{Pz*e+^Vn35gjKANX`VZbGtEF1@mp zyHtn?9Y*yw&-X)#Q@$sN44)IL_)H9L133@|g)y}-m#e_|C(W31&%j!=$8P@wINQNw zDUJTVe>c&tlsbKHS~#;qDF}O&k!#PnINct!uDO{GKZTU1aDL3E*aV-J-WsMSHK~Um&k|xT!c#SUdLC;__5|`1;=2xd*Ut8HJexbN901)R0f+Jv2-; zS*@#c-aG0JR}1(LcDdw`mXXB$d%k}zBs+dHtR;_Gl8nUS6!4{@MHj7E;x0+FFFnfV z0Cnt}$ny6-Wj_u(nt6qq1n(Qc)5fx-dEpn28BM|T9Y}UhDo{nh_hs0rK-uq;X$y&af#u3ojC6wr^92LQZA$hdzk}LVuN(kxJ z8ek^=&dT^F;)jb?M5k0pUVemZhBY)Dk;_2y!eGd#fspJPc9N**nbXS$hQ275c0$@Y87J!L>%OG_#P6-`Ku~Ve?$d~ zg2vdPQaq@AzP%`9g^qE**z!VqU(szk(-xqF!sNChy_BhB&~!1+H09&D8r}A<`YqND zQ+lIk#XYeFKrJ^&Z;5)np$JV5yM|bc(lso>ZG88d1t}=8XUG-(%eQDZuz^smmF+asPT@(Iz908Pw{!2R7Zks@% z$_wO`x7OmqqwOC;R;6#JKZnerO%vt3h_lfGrzrg6jW>Arf;MIvJu8uIT|dnJDHL|$ zSGz4wnF{W(lhVut$`!oePt4kZplt-K+I_$dB!F80qrobP*;fzzBj!p&gTm89$pw3r1Zn8n^->_-uvL?W-D z4Zn2oXwj_H>xnUg6w~|_0tPLy`bhUH+T9i2(d_Izr+~NbG?~_MPqMWuTlkmGfdpBB z6+eRNm2aF?*C1n^sb4)VDOWzFx;-?uLtOJv%c9!<5fVs44GYW}M8`zOffgTvXg_@phv(ivl+_E)C27`vk(B=$)l*TrN)yTJNuGQ2bA<4d7-T@P9*;UKC#%5cMWS zOC!)8t{XE4Wti6lxi-J*6mnO-7C29JeW~V5hlX! zmdFo_G&~}|Kn9W74Q=MYH=5l%U1iLou?bW9^m8#z%xk8ey6rjgj-Jc{pel7d!#QFW zt!Q62`g?m!zUrAZOf%g#-I|O3l_J8uRApH!K;j`P*^P zL9D1v|46SZo%w`dg?9sW&s1m~`tq`Dl7L1EX#WHHR)Ebv8!V!fj-61clCFl${fQeT zP9}r=_i}FseeFaF7!wF(j|of#g!H4@4X1(klBS=+?1&h7yu+8YTh~I1?vH2weWK(d zka^7+%31kzN0Z?ontdtm0QrG*QT#i81?arx@>Wx-aL&gSE zWbYsGp5Fvrup~z5h;%9McoY2Q0R!v34C0vAWA3qnzj9{=M;N~eA6X4ZtK_`9a)8n0 zmy-hi zMtLKH?w@pVxtX;!VNXT2M$~7G^B^)f{3^4A-+y|aZddbwZd`s-NPO~Ah#+!0nsDpN z+bRNguPCxaYhMmtS*%xjd!OCrtddtTTC68|EdhU1-IfGQ+h9mWyqwFD@9)~A@WZ5F z>DEzd8yQs;ujdIR1WrMGk-Lx^G5$hwSXyy&)w*WY<>^hg#D^CWlPXPNt()+Yp7~b2 zFSZ7_1J=d*vpf4F1Fzx}m>NQk^^f*2bS_15w%^F5gPHb^k^q+Sga{ZUvbpWZw}h(a&8}KKnJ2-pmrv;FD&;jmX5s~x$eXyvQpe58KU7(k{u&gRIFSv zA^i`pDMm~91=u@cGe@d@;JUuwq!8ewbl^0RS(mN}qb@x<(&T!{VBcF=r=%m|>qOJ1 za`8#DVHV{;x}~TgCfyXH1L*V#FDhSM&DYn8m%-(lf7wRiuVq{LCSic%myuK*9J)W< ze`HCCrbHo-Ro>W}7?lpRt5Fp54cW}B)8E*sCD)VvhWjmenEW$<|Ckn8Wxm9wUzOyK z?I+O*)WV;6pJHX6sbP>Fen56+2-Icqgt}s(n4mLKe_Zo?BE_p}hzhNuwnoI4jaQwL zfCN6$#4h-3si##&#Mp(pF)20Ks?-(IKp-W8``vKp+Yg~WNN$h_Q2AaTMgu%Wll0_SVfh4|TB4;B z))LHN_V7-9(NWFO?d4zR+ogO0R+I9W9j0gRy$Ig(Zs~5}%#<~{J~;J3&RtX^{%|o< zsK;#-uZUjwRxuHO?4dsIbszo{r?!#idw=v?NBhfWfgD2FYrqdo=t+?$oEFMEiS6hu zvLeKC?5%_^-Ja=1df6{6giv1rfhkXpF!1p0j#| z48|ntjH!^WgLeHb6C2)P_O7kjxR`KeE+cov+64vb#x?-_?fworm$1vBZCNu$9GcA{ z54t=&oDyX#?(E-^R~F9r!`-AlKmrc5y_~m0kI~uCi{}`c3ELFy{0mwyn9y^Axsns$Q(WNhNj+7E`~As_S;_*f@e^3vo;PgFN%nPm8ghxr7s( z1K=>l1Jo#h3X2kxM7y=0Rr&RGav;O`^-Ck|`WvgVBy;c{raGQ@v!=jw3}wHjJbtaJ zjeXs4B6rb{7kTKnr4emeC>YXPU$`+7V&l!3Z#|h5EW0b|UbDrW6(oG=M1gNGsK zJO3#B*QxIdit4K$YN<*1V&-E7L=&u&bmV7T4sqt1Fry2FEqycvcv27dbKqd<(EF80jM=HA&4i;Q=sV>25_d;PJg{ zP#N&VRY*LMX-Kj8V-?j^g3s+_b6Yk6<_Y8$ix2)hkZBWB$@Y&SvQ$#5+fN)Vj6_rK zNHrs1QhZcwjp9KqBIsz#ML@(;M_1oMGoW+-Xs-uy&{gmtX16inem}nBkb;YTwP;D7 z+BvUE@A#-!TS=7EjYhbgD#In_gJN>LK~nFH5i(T;cGCIsI&NV8x0spX4IWs z=Nj(qd(1sxmHw;Q%XCK-$#s}|_eNE<8!rP1FmPsu=S}f?Q+N4qT{5A*MC^slouaut z;aB<97%k3JMq^+DzvUqfRk9h|rbtI|P_mNWA&_?B^&UVL`*Nh}Kfm2QG%znthLAXINY%|&{JEzH7_a~4^ZWMIu6T$MMAs7KFc zfT*}6tPH--`kx#ud{8?Dj)nAH*7>htMYMe-C@6aOn;*fLK}C+G-v8CX8rTNMX#cOr zLB58Q?P~Lh&kW*G&BGn(M0Ts|HIGPS{yM19ykV!_L3T?T^tld&+<1?VfwYw9G1A_9EdJ1PD3Fcc-#CImy@hQciFhE3f=iCGnsWTn$N%b}n+A9_cTds( z-9bw-_kUXa?<5BQ47MF63)3h_h`P54q%h3XP~>9)dzQQ{yHO0gGGvZx#(6Igc~4Oo z`4oKlnd7TM0(una8HP))*O`1nFx?4eSgRGBtQ1uslE=jOzutC5|C&oM)S}p2G4ZuQ zZ#(AevUdRm)!4qCL;jBrhWBg`bG+2BFUX0SnkM{Fri_luIP6KN5*l>JOZ`0T!Q7R^ zEA=I=Et%rCdb=6(yLQ-q^q-vXZsmxL5DE_}G3~#CNVsg9UsEWJZmTkDJoF|E=+=am0m6Bsz88xkA0Y-N63Q@jypA-It zb+;>`>$A;GszTrMwf|^R&+|1jbgGaDto0%`5Lstlz;T=BRV^)?TE+6GP@ADCa1Y;i zoQu8^Y8kb2z&O5(D)xSV%|86z2cwZIJ+7fQ&;61F08Ih(tV{GJ!wD)zb;hwE_`D(Q zOcxo*sEtT~Y4$ylA&*P76FFvfDaiob=+E$QD#_&bxNR0&N?!elYI~}Rf1B`F?nK#z ze(Y(#!joxWc%Y=mkH8hi?D%xjP4$FBfHWkl=X#mT_{Pj?YsJu)!vpYeR5p9t7Wsgb zGS^it@y*-lo;{qaC>X;mGL2v-fA&_${;T$l%=)<{Ti0EPojrNK+SBlm2J~D^|@3~xivzM5koV=c`&i) zN}19bf_vwlRk-*jQSo%~%?t>hFo=&rxJwu)k@LPP@j6b5m3gS80z@Z5z%Ko8MzLwQ zFKc-)tk{K3udxSzEL!zgo)1cTQVv`}7Mo12L?BVatH5r@^KJ-^5|0NM)m|RlsuUu{ zhT4N5s}DBZ&NQAM>B+fmdZ#Vnm|9#(B9fr2cPN%?5s}NIRQ})%|35rfJP1;DRkUKZ zk0TGBl|f1sv5>RGPxch`s=HPR5o>r2B7xJizd0NwEhl|(es(SFCFT>;LVhV|z-PC{ zq#>Ac2Hs}TJYr+veOhYd(J0Dms?MSH>fNUtllRUEf^sCeKQ9C=A#=0#=)dB+TQE1X z;VF$O&(xf=P1?`}RSQJ~{+-UO{N}mUcg`Uxie|q8oC?yzNvFUU4?s^gzkWme$i4Ij zZ4M}peUexrreqqMp_i)S(G9gtx1=x_^gkZ@?YqB<;j6A0Zt$%|XvHmf_AS@Nnf~*< z+C1-@4JRoe%ah^RS2~$hI%Nlg_f-p;+x&L%J{EU~{yqyHy4r9gtOo$1c@JqOjDU`is3~GXq>(qge0o`dqRxaXdRE>*}&R|N^9zM!pbFi zCwB4x^oHUZ^TVpQ2+*P#d!0_jf@+%?`R@)3iqwK{ULq&tL5A3SrmNAJ@DE1Nu=UOd zq``PXl^;QWuKh72i!p05g)e#ul>un7Q1a}kgnxdFW;Yb|V#xmTgQgaK$YwxNJ&Bm8 z^+$&x0P~xxz~?~SwPsXvCIb>qqapRaYk!ep%OzcydJMJ?d$C|vp=alQdz4{BxB&@EZUvMLI~Oiz4BtBTRZuMmzyvfm~jduc@9L`5a#nh6$mFm)8nwj z$(|sIAl!MWWNJ{JPXxw;lT&USqfRldrQS8zn$2XCW2%WIR3H!|a6Z$$bhvtiQVIE% zaRs^^ah%W?eslh;<(r`JRjJM8$EfO%bC)N}xFb%1T)(;h`kX_}Ea6iWtPb!M4Vm_y zs#OZXxR*s{9vIK7GUb|5e*kX4rO$rh^*CL0WQLuE!Fa~?W7^EU)NjCQ(>q&OGP14n zKbizWPVxbNVUW2=y+47(a!_g0i5Z@|u;FQRmxlcgGlfAJGT9EPk*!BGz136UK@&g>$ z1N@Y*;yDpr;Bx3FP=FL<-#!>xt8hqRc;il|5afySxV;r6(`=uZ@$1Xmfg3LcFwsGu z;{H0Q;-V+V@LI5mCnBfd!-vlfxPKg^Xv;LC>eu23COJ9G!U;+);##VI z;NaOHqXJhuA(RPbaCfX{iuKD^VplGVw?)bdzM82}=J8gIV4?_nH8Pre|`FJ^t&i2DEykpNCxAcWL_|*?$J*0o0mF{JOqPIB% z)CSI7IQpGv$Mp4sibxq@|8X!NzgM9)z4zq__bs`+sTJBVYbfPk2SHWF3{Uab-q-Xr zC?0F(a;Q-ANL$Jih9@tu8+KGwIzaYj?kOr$m5PohlV4a}xgRUMc96@cT0&%?%sF%M z31yyW<-=1TzrpjxC`ZB3)6AYK2Qk0a-|AOQ%m_OX?G#_2%N!Z?MF0mw>^aKxUA{i` zR`PHkqL!W4h$&cMa}K@*C%W*=u#EB0tZ;QHLP0zGFt5JK?bO41-mx2Z z)9lktISdi{0qt!-WUC;c&z}abLrYKbaW+)F;4LcrDb{TST=jo=aa3%bV5~Xj&69-d z%%%0O!%s{_svZiws1N%IlDiy;dgC&0>?7=ZU^97`>eWSN3aChggu}T>fxoXm9E#0er{W|AU0Xu zt?S}0vA}FFUct!}0wQrqS8u(W-+G-X|M8|1Q3qW@7^SB+xK;3R1^#oi;36Q20Q8}s z?^T4{^0$+wE=Rx2#*<$X#mY~N-r2pW9=dzBKz@H>&r%LKRf2$yCy4U7K;-jLrleVW zk#W&d053u|0D2z!KTLlo(`3LaAO_2(%C%}4k*LNZ|k;5l%*ue z2a56@4@_|DhOoLcN=4Q5A%UNB?;zd)mZpz}(2MFF4zr~ccJWT0c;TNoeee?O36*qP zZBc-F@J2)TQ(B`SQE9s|x0tZGau{U^?cJj{K`l1DG=?Vw0Ol1Kp{?mW`K&Lg_D|8J z=P7rk&$DY0o;*FTL*%z&(9e|9c`JV)H{X6&Xau&-dKcdIT+GzlZ)CmN z!_V#W_J2LE?J`fZoC~6-R6Ib3K`=C*l*{IadUf{i6DSv(GX45w#7Y z0xTrFD8wp*-wOdfF@wXCVr_p5>M`X}3Are~WlQNhAUB0O^Pd(rGJ()T^#;zZLr6ov zNWI02vXtN-fL_cvq$|hpE6nOtmK^=Hj4eO!1;UR3wY20oh6WiCF32(UJ?O0Nw4pWP znY?6sQctA#)yNhfd7E0I?tME2@mntruoeP`$L!GuBacHVsE{VM>b?4$%*0SBC0Re7 zWtBF4&hRk>szF1K>}Q2pba6G~Yjg%N?Z1hf5NCw`?usnqE741}%iDhicChCY)io<( zi~_sUFWg62qU>O zz445+z&t+g&W#peRGx!lSx`xU3Tq45Fehe=RzgH^;C!imJ^Ex5~y znGEX;v+WkenR;YitGulA4?GMQ%QM7ND$EJtmG*RE=}@)gGY3;s523e zGWzb~+d%VBY18@G^>;^<#*)xi?-cL+9^Hjj&oIe^0ry=%Y?2A(tW8*7c)xOlxhD;! z8h&n{iZF4qQ2p@RY~LIQ5-5tMGQk5) zz}F-+uwue(A(rt_q&;RfF=b(gP#E;xF z<~Gn`!_T_>PqaF}!D}(#4;(o_Z;d79S>bxp&7d3ymt5pO^Kz3)t-B-6c85G6K9qf` z2X1)JbXl`Qp`nVeZ{5#rB?tMsfV`+SLmp~Ip*^%l(`51j;0%gElehG9$YxjN4vD6b z@%{^1KBkFz1XE4LyEoiptQTOWcgnpq(W?ucLLHb%bBT~RsI;5xLoMt(p}E?WIo*C0 z24hBNJcV?jCwB%}Lb=DB; zKIO?b)T9NtdhKOZJGcgR=8E9~pqsF?E*q*H$LtaLU%F)KYd-e3X->;Tc{)8ttdyU+ zAq;>{IHDku{zWW4>W3Q~U!|#mJuj;cUhq?f{a?~c#QQE95Tp3MBa*F?H3B#hVvD zm+7ovWXZFD)Hme)O7)#SZ!zt(KvU5*b>mg3V{_l){@}9VFt|;BL@WSxruRJ`xGnni z=UFh~DAmSXT*xf1?YQI{8C0CrBebIHz#i5(ztJy}n#$i>@43QYZvu2$7wPmC^-~K9 z@xF7PMzs7LXOPA5jXC1NWIQP{WiO5R&3r()(W4%(V&AojY_hlE0r`f6Pqi~&No@MD zR&r&Q5EgW#?(GlmRyBObo@78_)f!|1OsYHx#^lGLvU}zccgYjBt2e-r_C1;Xg^99k z)bw7lggONDg>e&bB0L22aR2)HNheIwmKEmB9@M!O=-wYJ0Hp0d{?QsSv)=P)SY8Oy z9h+z+`1E*~j#T~OZQTNjED5FMH%t+IUn&t%orXaLS?=F=gq(stI7x~Z9Ls@U^au1t zWEZ&1s4iE%WUuS?n+8SUYQ?yo8(xdEMO-$1Mq0o_S+N*tNy?Kt_&C#?ZsRxTE}ZFS zPy>%X%rhw6fed^#0b1Gre4HWP?!lKJ7?S13DWof=zG|uUW*Do;&@(<|JoPQuLo9UG zS0&ZYPGAw^3q(?e4kX}(1k4GNhgsRTt>BR<0tIWTJ2I34-r)Z) zCV{%&{LPL2$^2I9Lxx1LrigCWfE8NJ?YJl~Og>bN+f1G zy4L^@*hdg}e@oOhD_RsIEcDG99yq#fufRPLn8R9ju2&=#RTKeDUs}A>UgmFDW>b7wWW-eWx=r#24h~L$T+qwHNGoqC2R$Y zFkA(<)xT`xtv%g1MzoBw01u~;T;T-VP{1w5lMklQ zI1R3L-#!QVFBT$p0M)WgMWNhk88Qy7;sUt*Rrtw>+PTXvkzk&EDu>p^ECy#`&4Z36 zzS9?OH=I3#O6Pd5(OWo=@a1LLm7}7pXbp-rBN%k2d5(8 zGa5oa-e!8;Km_UNFAOC$`Uvg9Bicrn1-`8&FW^K3db5_SLdbWEgH%z!_>(2<%g`T+ zJZw!qX~v_MoinkC_hrF3(vZRLg@l*#6a6WS@vRvst>OA&;!o0gDii(y=s&L@VIMqu z!P3Q`oGh?564hVf5}cY9w?4sr`vCM8wSo8ZCP-5@H>tM~RoNEn@hLsnNRDjV~$GVyP7%XY^N4i&k{ zsKmgi$Did27vvd;XG?bQk?aYbRT-~oP~2dCbb0O?1ZSeY3U>aU2u9Xebh9%E$K>Q| znlN>(mazraa;>gGtFJLOurwV%f{B2TIgaJ26-32-t5CY6YEQ?_F`bRvOl2Tgk{Pmj zAn_kf0&Sr}LOKNWYnmrK+-g+_M&Z?yvPveOx|!fDgNg`pNWg|cT2RGhzkA{|;a&A{ znyOB;!&W|mcKiY8Tj$PPaN?R%9DCuqLS}`^GyFKBMO9L*Xij}qZKY1e;K$rM=tN!1 z`87V}an%TO7>>Qn=w|sg53z}yg)5OVo;7#g2_SpXd3fln!~3Qplgu5d8}ApH>34~P z7P53%lG!3Pf4eNe!S`CT0bl#tYOIh5X~K+l(Dt@{jU|-^@ZtS_5K;*@dx0_kCT2Y| zk!YpAuFaq4(VhOpJJ5roz*WwP_-jU*K}L2G*rY#9|J~5kS|6@zVcu>px8zb@;8~pf zc_}06m|t+1ho{oV07SxS9f0y%-2IE%6!9Zf>Y%xPQoB9b5YN%P?%i0os=0lD>FAyz zoW~86)3vh5?v`*@$@c3PqAp&piP@WLM-rg9JxsNS z20O@~!KYnj{SBR@`g2vT37t*qhq^@LQRVbiG#EozWUP$`tjeXj&10D45^{#St_^It zOrDkafWoJt>u|?zI&Q}3?hhPpl9Xev{Qgq+VHG#A{-SBhR+x;w(oJ5xoZYi{ zfawTEUb4D<$4$&JMv;@scd(uB#$WBM(~c>e4U`LBvg|=Xr_%zz_8xTVL>PlqxHU_5 zSSl_fXD;qgJ6<>-Uz^wHpB6;0>?>!M(PNwqYIW=3-^_fd=vE$p-rl`vrron|Al0G--m99}3JY79}^MC?rj1xmq}YQRC1fGK^}7UvnBlLNAM zIoe0>unld{re+iH){Ipsi)$czOIZ$KG%5^AZjI#eED1>MTG)&Nis^yS)ntWFRBuYG zNx-h%^WZgWyD+imN^qAVJ}o)cY)Gf*3;ygHf- z*4$T%Vzx-RQgZOKidzp)SDOn4Oe%BP{`Mt9uPX^;0c$)lRB5&L!w6@TE&l^+y_+?o z59ERh==Xiv8RSboWs?c|Ddl*3K;bX#UE6*w$&%dyY!g5IJC*PVW3Y4UqzLMwUbsCZ zq)Q-xk9@eHm)77=!m}Hdc%&<%J4I6OqdwsZL1 zl+NcmxJyI;)A68T^+RtQaO_D0SIG%(n4%)|hqhhqNG3sTL*cdkko$t(x$MmP+4vN5 z%9AhE+VEiH6^-*m^)fEFr#s5qc9lN<1>I?+n#1O5gQ8Qg4~|0HmXv^OaO~ovR)i|X z?9X~@OF%Wq&RVfcX>2RU3g@K`x7v*Sg~a=ML$sh`N-9fQdHISPz$7oJ!K5K%M{f}d zmfwmjf;axHS~(O*zZ%s)maK4e9zyoJK=7ZyZ@nja%J=ClN5bsvXPvOaaYy_xAic?f ze{bjo=52;!Ka(e*>XW}t?T{Zne`y3<{R00#xMM(_EpV3k3!WG!Y- z9L{o8sj*hv<+pbsg`3I~XDx|JZ2b-=SgE-?V?epW$!SI;8?;#X!*sXPYY%o&X`{b1 z-ZpB)ohL3H#A=(xZ(JL^a*Mj~;6UJxevFt?10wN2#Y15`BL3ERCRVATRJj2SubihV zsf1!2C4+*?1`j^@b9@est?vA#Z~jPmnfcLjHFwGnGl9yNU#Zd$hdM-_6hWd3oDeD) z3JBBnXB)G&9X>gI+|v4BrWU{Snq^fBfHbNCLUr)$Y;G=J<#s37uRHD>UTTh~Ni@_2 z;{oXXT$|C@u;j)Stl z*_*qh{g&Pv#KUU1;H^9|9psS%K=(|ejHB9)dgLD<$45R@rr-<9@Ld%n|5qr#z{>R zo?SJ{eh6^bJ#JxD@wPQjS2X}Te;;1(*NR*}Dnu5uE?XQiic&DvYziAki%ZO$`Pq)D zWRPkA=LOF)RY|W#Ow28{^FI0hIL^392$O1eR&gkD))9hw-45^|)wq$Xq*d{Y?mk*O zYo|MRoluRGLE<-QB-1PjzmFA=X*#H3ngje=2z`r zAEdRue>+9*Qz2L=YlFzr3@p*& zC@5P*J*x#U4v=}^AJ9J-WcwHg1Uj-^0wUs<-%yJV;Ohs zEH+#SUoAjU(ItaHpIbh=K&iCBU2ojBr}NvwCLuKV%3VjGv3zCI+5Y^59c=tP=$2j2 z`{%f2N>g++4}C5p${9w#Ld&UrqFf-&CbWE>4#;}o9~@(y{CZ(ZJ#oY+eiyewpCO2yuxZ%Bn7FIL;jr{GY6gA_;SOPjwPfCY$&)qwuN27h6*mI-0`z zg{F^wcdn8`4aS_~3n0-q2~-kslFlniKY0T8#r(B<1d*_*F8k>_Zt{mof~bl9O;jbVDkFRaO;G=J>nsDFrpnB zWEl^=#8ov1YgSJ7j~7II;<-&zm^N-b}piuliJy6r)XI&jPjyScia-xQDc+ zmDJ)_EStTy8mEthq8<|mc9T(15!t}sZXq`KZrKpiG~auGuY82}9=;TTAJB9L|33uu zx!>QSjsVZv_s$o-JDA8QDGQ}HUi*}QHFIc20d9B?x)WtaBFUQev=oqeV$lJ0 z%h=UlMUT(FsajQY#bW-#6u4H2Jb2L0wsbo_ekGJq%~?+>vg7ZvFEA#K^-W_lB9VleTO)pTBs18-dVY?|z5G%MMCPmCqAibQ z6XEt&OPHlr6v#Br6F$dfK8SdUCQ7CZ(7H>lKYaz^Bs&|~_7Hl{=vQy4Q*{tMaHp7o z3&c#02xfLiWmyx~J6t$eSfWBjT>aBk8kx;!Z*jZS07=0AWYfR4u5Y5>A8ud;zUD}h zVC4>y{~e9i0&M6#)KYP(H8J2!_3#c zD%Wp?Yt>tl!A~&KWeShen)kh(-Uoy3M#1|cUb7}w(ou5SBu%^lWtqw=nI7?*no2pE zg_%BNKkrY(I}Dkl#IwG#;|IX6 zF1o{q=%dDfxg{>esE8w`I`W>TPksRyWEy9_l)Y&qCicPPAVyR86*OsDnee(ut zJa{U^dcZb;mby5>oec>d@>%=c$fJjoTqgw}m{&~4NS*;AK}F9kK0^drhKr?Wm2M?F7UZf!Fw}Q|lO%8F;)!O8&If zfdQ^XUpM@!ib0+tP*>O80P1g6rflDiv~H)9MHV*QJVGuHX9}NW^dFkxr_6$`{0T|? z1PMEzXjLJ|D|u-p3E1Pg!y>P%!_P#oU%HWQX5PZ=f^E7d0tGy>)}&v3R7cNca?hb; zjPF)aIm=Tl*EgC}mZJ<j$6Ulyk{F= z(p}*4C#GS+ju%L6pmUhuJbDoVoBYWbgd*}+%U-{o@3S3x?|17Up!=p5ne_(29>h#*gS4n_Bht4$xRp?U4yX<=CO!G?(IWf+bn*lJZA9ix?jhDk zaQ;#yV`x+GJ-O-yEws1Z(`hBARN%+_0e$z$=7rT8Ox8>xQ!Tr1NFak_q~=xFwPxHI zxS)83Tj0R;oy|^&1pFgAlWW~`TRBNch1SAC=K(Xdo0q^^aPg3V~%$^ zVoj~Ya`+)khkEfI{8|X48g;~j3{jFdHl%Dz5VI%V#}qvVPg10_lQfG#up|g)bj6_kJ1-mjm_MMG z?a?t>F6bIF>jkoT!#;1JGVTgzBFMuMpC_q=x&YcYmvcT$?~`OBvXPb zobTT0nC=IQ{OS~+Xa+o7e#3t1cS>Q1q2e%g!G1PtXFx+-bb$VYgu$9tBBU1=>>*v( z@C+T*l%o8;!v4Hx&~JlSCZmEovM#vQtaf&lvQe;2_n^C|i_A-iN%m36tx?0Fnz^Hj zPIEhfk`=?dD%V^uSYQAFo1fKO?0jhSjNiw*3C6Jq>~hKlyx+dX^j2ZED9P=$2YArH z8-UON0|N z-y`pEt<_V(SLH?i&g9?APm$u{wJB@xUc;kApT!1OW!60ay_cm1EAD5W^Ts+Q{6@mp zpK{t|q2!!Kgwxb-FGl^0O(1geAE;t1mi1~rak`Q zf!R4Q>pCF-k?V&m(QGNoxr58! z`V0EI?6KH8l(3R$OiB0L@B=7TJxmk!Z#1N`FER=xWzU3xLmKH!2NdGz_;g#`pmZry zIW5sdS4%sQK=ZbX3Q>Gf^#5oQsFUjNJUxr1Bq27RX1Zmc5=I449)qHk8;}NG!EOQ@ z2I-c=$2gX#(G)hd6>SD4sd^!rm-P1Jd0e23<3e(F+Nb=?;1UTBNW<9x7a zGni^lV9;asC@KX@O3Kw3sg2YX3W*YTwmd_Yjndzyx#axze7W-%bceDZm?5Yr-dk+j zKiuW$^C%GcW26Gz)e5mu69VAFyMd37<5UK@A=G<75PF7dXD3lINrys&u||v3?be(4 zd=sqLoTEblciu2(zXEGMvEE6C%+b?LQ9<;*=2$+_s`ImsG1w$f-FPu=+ht6sSQSNQ?x%3=elXQPqiUo8|$ zb>3R}Hfc#ilOM7?FZJ{c&?rHGEJXe=J$aLd<#YK4nvaIw1s)9%A>gXd?zQG_4%673s~o`A=4MvYP}~b4E7eSXNnY6ku1zX6?!+l#l+tP`WSbc zRVdxT(}?@&Te^A{$PD5HxP-<{Qw z%o_3*6(#0!hUy5W^H{&UXr;QU!n;W&-eI%vR^-nB@}5uwuNUxCkl=Ml`S_rD;g;I6 zYc}EGQjscWbp%PgrvX|uDlj00vnFij4v?K|kD>7=6r1ebw0=;?R@09{eKwyBrj;`S zW0@yOx03wEOqG2Q$^o=@Ou03;m?n z^?5-p%-HiO&L)Iu<0GxwinpyYuEigWeiIN$p2R_zp7CIDRL2+3txN8wrYF^Fbb;XR zxOmyl8B~g$Q^jyZxi0h*!%hZ+`+X9wjhpZ8Z zL_9)FZZVbZOGK`LeeXAw#KD_x#;vN=aV@8kq%Co#KJNUXxfU#8o|fJ)^k7ouu}Zv;6MYYTF`^JEyz z+Rtr}+bI!4oCtopBWVL^gV2b1zvh-KDL7~xyjrMQ@lsX-NwdDv)iJJ`W(s`R&! zBn8R1nuEAf1N^sXLGSAXyVY$DFls_Mg=!wwlIlE4AGbZ-WJQ};8C&+tE2g79ouhyW zMpc@}z~7k^T?dgMu*UACCG0zLCCSt1Bs%KtIZhZ*(7!EOywPsWdVvfsQTAVK0#RlB zJ>lh&p;W(aW2<%i`SCe(`o2*i^Tuxg^wFRHERI0NUic06)OJFW1W)mzwNb0<{AeD4 zZdd8xx5KSDI`@_kH^R1wOQ7RQ>>FBVRO_l^OJ~%b1GsnogqKCqVUTZhT!Nn~6{FRx zpdVgCUE z;9;*)2H(13x;`nsHvEj9!RF}PsV0}KcLH=Q1LAR4?=LXw)t%kmo(h|p40AfUY?=nc zi)y)ZocRj*T0dWfdZkIF{3XI;x}+!TSFo+9VUeE;a`F}JC)Lm)|1I7Z(<33$+hQ@VF zZFZSUeYaZS3|mW_`zIsN=j_@G51nFN{LBQxoz;66h7rRsXJKzijLa`Zi6{(T0#r;+Z8p+BNNz`5Ds5E2dl zN`Tb<0qB%uS=DOEoxASgXJjnGM{K9B_|;NfDL!uOV#;AdH^5ZM*K1h}FKI(PfgYE|0+-rGeMdk)ia41FX(qsJHgsj*lrS{+w-s~t>^Bpa?K;vP z35?}SAb|2lz02bExNHAhU^)~21ykI*eD#%ODamad$eFF(A^sZd;s4L3O2DRjBJg!v zI`Rnoyo!CVog@+$$KLMu94da0n_XAbak~JwXaQJ}8uwhes^M&GNI#!PZq*k?-|8?r zc{TI7x3J4e1*7(bY4iHh6*1 z0cJ&41=5cKH?dU=qr2T2+67LBH#_OGiWjX75yq3oZBw6Qz_@hvgu&Rld!vYlOi2AL~Jd0IkdPIJlXJY^3PyQ*Jhr$+LC1;Wcq&3`79 zZQ|X#@9B!{LhweHj$TVGwHUjmK4AKFj<=m}&X8a~jLGkX=Lv1Rc67(-)}|zempu!$ z{P3jU7u|y{?c_m_xZL&({=${$>9x4r5Lpy;*F-c_r%d4uqbCD5Ky+dlvCwhyFn74z z>@PTyeQ##%qu7^o+o;@-^{c0$z)Nmp#4ci467{iMf_MIe!|(j?t=P*>_gi8*_QlWT z@cKl+4#qS$8=7bk}7@G*jiWt#XRSTQ6;;{E|d}YfqwUuJJ_1TBI{@C{sT)58;`ak z-(UlvpSUV8yq7{xJ|B)^6G_-G5V}^mBsz1Q{k}pl@pCK?qTU8#X$2#35Bkp=C?!HA x`O_an(_PrDOP{d=ye)!?s%CBfX`%eO>mmZ$j3s}28cnsIsq?zjiRvEo{{wr`*R=ou literal 0 HcmV?d00001 diff --git a/src/tests/blocks/00000000000000001203c1ea455e38612bdf36e9967fdead11935c8e22283ecc b/src/tests/blocks/00000000000000001203c1ea455e38612bdf36e9967fdead11935c8e22283ecc new file mode 100644 index 0000000000000000000000000000000000000000..7d65942d8ef7cc5897d4c7bbe6578f5c6b3ee77c GIT binary patch literal 99980 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 sn!KR@sP0Qx0Kvj6}9 literal 0 HcmV?d00001 From 040e685c200d0be2d6b071503a8f89ae81428c83 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 23 Apr 2021 21:00:20 +0300 Subject: [PATCH 004/113] Acquire lock closer to its usage --- src/daemon.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index c02ae10..32c5434 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -250,8 +250,6 @@ impl Daemon { B: IntoIterator, F: FnMut(BlockHash, Block), { - let mut conn = self.p2p.lock().unwrap(); - let blockhashes = Vec::from_iter(blockhashes); if blockhashes.is_empty() { return Ok(()); @@ -261,6 +259,7 @@ impl Daemon { .map(|h| Inventory::WitnessBlock(*h)) .collect(); debug!("loading {} blocks", blockhashes.len()); + let mut conn = self.p2p.lock().unwrap(); conn.send(NetworkMessage::GetData(inv))?; for hash in blockhashes { match conn.recv()? { From bc14b7cfdd133d631bcea95f98332bac4f7871a6 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 29 Apr 2021 20:12:32 +0300 Subject: [PATCH 005/113] Bump prometheus --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5308e07..c5fe243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "adler" -version = "0.2.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" @@ -414,9 +414,9 @@ checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" [[package]] name = "hex" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "httparse" @@ -612,9 +612,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", "autocfg 1.0.1", @@ -701,25 +701,25 @@ dependencies = [ "bitflags", "byteorder", "flate2", - "hex 0.4.2", + "hex 0.4.3", "lazy_static", "libc", ] [[package]] name = "prometheus" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8425533e7122f0c3cc7a37e6244b16ad3a2cc32ae7ac6276e2a75da0d9c200d" +checksum = "5986aa8d62380092d2f50f8b1cdba9cb9b6731ffd4b25b51fd126b6c3e05b99c" dependencies = [ "cfg-if 1.0.0", "fnv", "lazy_static", "libc", + "memchr", "parking_lot", "procfs", "protobuf", - "regex", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index aeedefc..01b85ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ dirs-next = "2.0" env_logger = "0.7" hyper = "0.10" log = "0.4" -prometheus = { version = "0.11", features = ["process"] } +prometheus = { version = "0.12", features = ["process"] } rayon = "1.5" rocksdb = { git = "https://github.com/romanz/rust-rocksdb", rev = "379c2d7f2a15fe31d2ec2726f4b6179de5c8c287", default-features = false } # to support building with Rust 1.41.1 rust-crypto = "0.2" From 8890c4bbb78d2de6a6f3e9e2b5ef8094106745a4 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 29 Apr 2021 20:26:29 +0300 Subject: [PATCH 006/113] Allow disabling prometheus' process collector It is supported only on Linux. --- Cargo.lock | 7 ------- Cargo.toml | 4 ++-- src/metrics.rs | 8 +++++--- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5fe243..97d8a1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,16 +719,9 @@ dependencies = [ "memchr", "parking_lot", "procfs", - "protobuf", "thiserror", ] -[[package]] -name = "protobuf" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e86d370532557ae7573551a1ec8235a0f8d6cb276c7c9e6aa490b511c447485" - [[package]] name = "quick-error" version = "1.2.3" diff --git a/Cargo.toml b/Cargo.toml index 01b85ea..34d06ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" build = "build.rs" [features] -default = ["rocksdb/zstd"] +default = ["rocksdb/zstd", "prometheus/process"] [package.metadata.configure_me] spec = "internal/config_specification.toml" @@ -28,7 +28,7 @@ dirs-next = "2.0" env_logger = "0.7" hyper = "0.10" log = "0.4" -prometheus = { version = "0.12", features = ["process"] } +prometheus = { version = "0.12", default-features = false } rayon = "1.5" rocksdb = { git = "https://github.com/romanz/rust-rocksdb", rev = "379c2d7f2a15fe31d2ec2726f4b6179de5c8c287", default-features = false } # to support building with Rust 1.41.1 rust-crypto = "0.2" diff --git a/src/metrics.rs b/src/metrics.rs index 3879406..9871a8e 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -1,8 +1,9 @@ use anyhow::{Context, Result}; use hyper::server::{Handler, Listening, Request, Response, Server}; -use prometheus::{ - self, process_collector::ProcessCollector, Encoder, HistogramOpts, HistogramVec, Registry, -}; +use prometheus::{self, Encoder, HistogramOpts, HistogramVec, Registry}; + +#[cfg(feature = "process_collector")] +use prometheus::process_collector::ProcessCollector; use std::net::SocketAddr; @@ -66,6 +67,7 @@ impl Metrics { pub fn new(addr: SocketAddr) -> Result { let reg = Registry::new(); + #[cfg(feature = "prometheus/process")] reg.register(Box::new(ProcessCollector::for_self())) .expect("failed to register ProcessCollector"); From d1d6db450c62c73322f47d034d547023ec195944 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 8 May 2021 11:14:12 +0300 Subject: [PATCH 007/113] Allow disabling mempool sync --- internal/config_specification.toml | 4 ++++ src/config.rs | 2 ++ src/tracker.rs | 6 +++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/config_specification.toml b/internal/config_specification.toml index c8a7e99..d69c2cc 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -98,6 +98,10 @@ type = "usize" doc = "Number of blocks to get in one JSONRPC request from bitcoind" default = "10" +[[switch]] +name = "ignore_mempool" +doc = "Don't sync mempool - queries will show only confirmed transactions." + [[param]] name = "tx_cache_size_mb" type = "f32" diff --git a/src/config.rs b/src/config.rs index 7d54672..950d762 100644 --- a/src/config.rs +++ b/src/config.rs @@ -129,6 +129,7 @@ pub struct Config { pub monitoring_addr: SocketAddr, pub wait_duration: Duration, pub index_batch_size: usize, + pub ignore_mempool: bool, pub server_banner: String, pub args: Vec, } @@ -233,6 +234,7 @@ impl Config { monitoring_addr, wait_duration: Duration::from_secs(config.wait_duration_secs), index_batch_size: config.index_batch_size, + ignore_mempool: config.ignore_mempool, server_banner: config.server_banner, args: args.map(|a| a.into_string().unwrap()).collect(), }; diff --git a/src/tracker.rs b/src/tracker.rs index c00f978..4d7ab5d 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -23,6 +23,7 @@ pub struct Tracker { mempool: Mempool, metrics: Metrics, index_batch_size: usize, + ignore_mempool: bool, } impl Tracker { @@ -35,6 +36,7 @@ impl Tracker { mempool: Mempool::new(), metrics, index_batch_size: config.index_batch_size, + ignore_mempool: config.ignore_mempool, }) } @@ -64,7 +66,9 @@ impl Tracker { pub fn sync(&mut self, daemon: &Daemon) -> Result<()> { self.index.sync(daemon, self.index_batch_size)?; - self.mempool.sync(daemon)?; + if !self.ignore_mempool { + self.mempool.sync(daemon)?; + } // TODO: double check tip - and retry on diff Ok(()) } From 32e5829d824e2e491fc456a1141e23b84c9ce906 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 9 May 2021 14:05:01 +0300 Subject: [PATCH 008/113] Link to electrumx-spesmilo.readthedocs.io --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e5356e..b14fc60 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ thus preserving the privacy of the user's addresses and balances. ## Features - * Supports Electrum protocol [v1.4](https://electrumx.readthedocs.io/en/latest/protocol.html) + * Supports Electrum protocol [v1.4](https://electrumx-spesmilo.readthedocs.io/en/latest/protocol.html) * Maintains an index over transaction inputs and outputs, allowing fast balance queries * Fast synchronization of the Bitcoin blockchain (~2 hours for ~187GB @ July 2018) on [modest hardware](https://gist.github.com/romanz/cd9324474de0c2f121198afe3d063548) * Low index storage overhead (~20%), relying on a local full node for transaction retrieval From 271a7d4cd103559593ef11c6a6fce95a360a8047 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 14 May 2021 13:26:57 +0300 Subject: [PATCH 009/113] Add more logging in case of p2p errors --- src/daemon.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 32c5434..305fa9e 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -44,7 +44,7 @@ impl Connection { network, }; conn.send(build_version_message())?; - if let NetworkMessage::GetHeaders(_) = conn.recv()? { + if let NetworkMessage::GetHeaders(_) = conn.recv().context("failed to get headers")? { conn.send(NetworkMessage::Headers(vec![]))?; } Ok(conn) @@ -69,7 +69,7 @@ impl Connection { trace!("recv: {:?}", raw_msg.payload); match raw_msg.payload { NetworkMessage::Version(version) => { - trace!("peer version: {:?}", version); + debug!("peer version: {:?}", version); self.send(NetworkMessage::Verack)?; } NetworkMessage::Ping(nonce) => { @@ -224,7 +224,7 @@ impl Daemon { let msg = GetHeadersMessage::new(chain.locator(), BlockHash::default()); conn.send(NetworkMessage::GetHeaders(msg))?; - let headers = match conn.recv()? { + let headers = match conn.recv().context("failed to get new headers")? { NetworkMessage::Headers(headers) => headers, msg => bail!("unexpected {:?}", msg), }; @@ -262,7 +262,10 @@ impl Daemon { let mut conn = self.p2p.lock().unwrap(); conn.send(NetworkMessage::GetData(inv))?; for hash in blockhashes { - match conn.recv()? { + match conn + .recv() + .with_context(|| format!("failed to get block {}", hash))? + { NetworkMessage::Block(block) => { assert_eq!(block.block_hash(), hash, "got unexpected block"); func(hash, block); From 66f53e52b9927ddfa02818147e37fed3f58191e5 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 17 May 2021 14:11:49 +0300 Subject: [PATCH 010/113] Fix a few clippy warnings --- src/bin/query.rs | 2 +- src/cache.rs | 7 +------ src/electrum.rs | 2 +- src/status.rs | 7 ++++--- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/bin/query.rs b/src/bin/query.rs index c1f73a0..7287859 100644 --- a/src/bin/query.rs +++ b/src/bin/query.rs @@ -16,7 +16,7 @@ fn main() -> Result<()> { .iter() .map(|a| Address::from_str(a).expect("invalid address")); - let cache = Cache::new(); + let cache = Cache::default(); let daemon = Daemon::connect(&config)?; let mut tracker = Tracker::new(&config)?; let mut map: BTreeMap = addresses diff --git a/src/cache.rs b/src/cache.rs index ec9de39..ca4ae89 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -5,18 +5,13 @@ use std::sync::{Arc, RwLock}; use crate::merkle::Proof; +#[derive(Default)] pub struct Cache { txs: Arc>>, proofs: Arc>>, } impl Cache { - pub fn new() -> Self { - let txs = Arc::new(RwLock::new(HashMap::new())); - let proofs = Arc::new(RwLock::new(HashMap::new())); - Self { txs, proofs } - } - pub(crate) fn add_tx(&self, txid: Txid, f: impl FnOnce() -> Transaction) { self.txs.write().unwrap().entry(txid).or_insert_with(f); } diff --git a/src/electrum.rs b/src/electrum.rs index c29e58a..6754813 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -79,7 +79,7 @@ impl Rpc { ); Ok(Self { tracker, - cache: Cache::new(), + cache: Cache::default(), rpc_duration, daemon: Daemon::connect(&config)?, banner: config.server_banner.clone(), diff --git a/src/status.rs b/src/status.rs index df7e226..2e78742 100644 --- a/src/status.rs +++ b/src/status.rs @@ -65,9 +65,10 @@ pub(crate) struct MempoolEntry { impl MempoolEntry { fn height(&self) -> isize { - match self.has_unconfirmed_inputs { - true => -1, - false => 0, + if self.has_unconfirmed_inputs { + -1 + } else { + 0 } } From df1e56b7868a0e1ed2f6a195b8b39db836ab6b8a Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 20 May 2021 14:03:54 +0300 Subject: [PATCH 011/113] Update dependencies --- Cargo.lock | 256 ++++++++++++++++++++++++----------------------------- 1 file changed, 118 insertions(+), 138 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97d8a1a..f03d01d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,18 +8,18 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.35" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" [[package]] name = "atty" @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "bech32" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" +checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" [[package]] name = "bindgen" @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aaf87b776808e26ae93289bc7d025092b6d909c193f0cdee0b3a86e7bd3c776" +checksum = "3e6d72ba9671cb0929b5620e1bc30cdf5e206ccb3dbaa8964d67f6efc1e16129" dependencies = [ "serde", ] @@ -108,7 +108,7 @@ checksum = "0d708433972bf78bd5f909d1d288f9ac1cceeab1460edb954e962f83e1f440a3" dependencies = [ "bitcoincore-rpc-json", "jsonrpc", - "log 0.4.11", + "log 0.4.14", "serde", "serde_json", ] @@ -133,9 +133,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cargo_toml" @@ -150,9 +150,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" dependencies = [ "jobserver", ] @@ -166,12 +166,6 @@ dependencies = [ "nom", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -180,9 +174,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "1.0.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0659001ab56b791be01d4b729c44376edc6718cf389a502e579b77b758f3296c" +checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" dependencies = [ "glob", "libc", @@ -232,16 +226,16 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -251,18 +245,18 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", "lazy_static", "memoffset", @@ -271,12 +265,12 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" dependencies = [ "autocfg 1.0.1", - "cfg-if 1.0.0", + "cfg-if", "lazy_static", ] @@ -286,15 +280,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", @@ -320,7 +314,7 @@ dependencies = [ "dirs-next", "env_logger", "hyper", - "log 0.4.11", + "log 0.4.14", "prometheus", "rayon", "rocksdb", @@ -339,7 +333,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime", - "log 0.4.11", + "log 0.4.14", "regex", "termcolor", ] @@ -350,7 +344,7 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crc32fast", "libc", "miniz_oxide", @@ -382,13 +376,13 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -399,9 +393,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "hermit-abi" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] @@ -420,9 +414,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "httparse" -version = "1.3.4" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" [[package]] name = "humantime" @@ -469,20 +463,20 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] name = "itoa" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "jobserver" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" dependencies = [ "libc", ] @@ -519,17 +513,17 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.81" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" [[package]] name = "libloading" -version = "0.6.6" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "winapi", ] @@ -546,9 +540,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" dependencies = [ "scopeguard", ] @@ -559,16 +553,16 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.11", + "log 0.4.14", ] [[package]] name = "log" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", ] [[package]] @@ -588,15 +582,15 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "memoffset" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" dependencies = [ "autocfg 1.0.1", ] @@ -627,7 +621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ "memchr", - "version_check 0.9.2", + "version_check 0.9.3", ] [[package]] @@ -653,11 +647,11 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "instant", "libc", "redox_syscall", @@ -685,9 +679,9 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] @@ -712,7 +706,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5986aa8d62380092d2f50f8b1cdba9cb9b6731ffd4b25b51fd126b6c3e05b99c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fnv", "lazy_static", "libc", @@ -730,9 +724,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] @@ -868,9 +862,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" dependencies = [ "autocfg 1.0.1", "crossbeam-deque", @@ -880,9 +874,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -902,15 +896,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.57" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +dependencies = [ + "bitflags", +] [[package]] name = "redox_users" -version = "0.3.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom", "redox_syscall", @@ -918,21 +915,20 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] name = "regex-syntax" -version = "0.6.21" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "rocksdb" @@ -994,9 +990,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "secp256k1" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcfd0eece5bc8fca7ca07a0c17ffd334b028f72ffbe9dd8ec924a8c885753278" +checksum = "ee5070fdc6f26ca5be6dcfc3d07c76fdb974a63a8b246b459854274145f5a258" dependencies = [ "rand 0.6.5", "secp256k1-sys", @@ -1014,18 +1010,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.118" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.118" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote", @@ -1034,9 +1030,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.60" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", @@ -1051,9 +1047,9 @@ checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "signal-hook" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa894ef3fade0ee7243422f4fbbd6c2b48e6de767e621d37ef65f2310f53cea" +checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" dependencies = [ "libc", "signal-hook-registry", @@ -1070,15 +1066,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "syn" -version = "1.0.54" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote", @@ -1096,49 +1092,39 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - [[package]] name = "time" -version = "0.1.44" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "tinyvec" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" dependencies = [ "tinyvec_macros", ] @@ -1151,9 +1137,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] @@ -1181,18 +1167,18 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" dependencies = [ "matches", ] [[package]] name = "unicode-normalization" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" dependencies = [ "tinyvec", ] @@ -1205,9 +1191,9 @@ checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "url" @@ -1228,9 +1214,9 @@ checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "void" @@ -1240,15 +1226,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "winapi" From ce0018be5787e03b8f2c9c37a01178b29be68fe3 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 20 May 2021 18:20:14 +0300 Subject: [PATCH 012/113] Fix rocksdb build on RPi4 --- Cargo.lock | 43 +++++++++++++++++++++++++------------------ Cargo.toml | 2 +- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f03d01d..dfd646a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,12 +62,13 @@ checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" [[package]] name = "bindgen" -version = "0.57.0" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" +checksum = "75b13ce559e6433d360c26305643803cb52cfbabbc2b9c47ce04a58493dfb443" dependencies = [ "bitflags", "cexpr", + "cfg-if 0.1.10", "clang-sys", "lazy_static", "lazycell", @@ -166,6 +167,12 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -226,7 +233,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -235,7 +242,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -245,7 +252,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] @@ -256,7 +263,7 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", "memoffset", @@ -270,7 +277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" dependencies = [ "autocfg 1.0.1", - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] @@ -280,7 +287,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] @@ -344,7 +351,7 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crc32fast", "libc", "miniz_oxide", @@ -380,7 +387,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -463,7 +470,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -523,14 +530,14 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "winapi", ] [[package]] name = "librocksdb-sys" -version = "6.17.3" -source = "git+https://github.com/romanz/rust-rocksdb?rev=379c2d7f2a15fe31d2ec2726f4b6179de5c8c287#379c2d7f2a15fe31d2ec2726f4b6179de5c8c287" +version = "6.15.4" +source = "git+https://github.com/romanz/rust-rocksdb?rev=4554d19b2ff2e34493564b4d868454097c74b693#4554d19b2ff2e34493564b4d868454097c74b693" dependencies = [ "bindgen", "cc", @@ -562,7 +569,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -651,7 +658,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall", @@ -706,7 +713,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5986aa8d62380092d2f50f8b1cdba9cb9b6731ffd4b25b51fd126b6c3e05b99c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fnv", "lazy_static", "libc", @@ -933,7 +940,7 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "rocksdb" version = "0.15.0" -source = "git+https://github.com/romanz/rust-rocksdb?rev=379c2d7f2a15fe31d2ec2726f4b6179de5c8c287#379c2d7f2a15fe31d2ec2726f4b6179de5c8c287" +source = "git+https://github.com/romanz/rust-rocksdb?rev=4554d19b2ff2e34493564b4d868454097c74b693#4554d19b2ff2e34493564b4d868454097c74b693" dependencies = [ "libc", "librocksdb-sys", diff --git a/Cargo.toml b/Cargo.toml index 34d06ba..f559ee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ hyper = "0.10" log = "0.4" prometheus = { version = "0.12", default-features = false } rayon = "1.5" -rocksdb = { git = "https://github.com/romanz/rust-rocksdb", rev = "379c2d7f2a15fe31d2ec2726f4b6179de5c8c287", default-features = false } # to support building with Rust 1.41.1 +rocksdb = { git = "https://github.com/romanz/rust-rocksdb", rev = "4554d19b2ff2e34493564b4d868454097c74b693", default-features = false } # to support building with Rust 1.41.1 and https://github.com/romanz/electrs/issues/403 rust-crypto = "0.2" serde = "1.0" serde_derive = "1.0" From f6fac2478c599e3cfb9627f25d11f4151fd4fabb Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 21 May 2021 15:37:47 +0300 Subject: [PATCH 013/113] Skip first empty histogram bins Following https://github.com/romanz/electrs/issues/404 --- src/mempool.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/mempool.rs b/src/mempool.rs index 6f82f2d..49e931b 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -188,12 +188,34 @@ impl Serialize for Histogram { { let mut seq = serializer.serialize_seq(Some(self.bins.len()))?; // https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#mempool-get-fee-histogram - let mut fee_rate = std::u64::MAX; - for vsize in self.bins.iter() { - let element = (fee_rate, *vsize); - seq.serialize_element(&element)?; - fee_rate >>= 1; - } + let fee_rates = (0..Histogram::SIZE).map(|i| std::u64::MAX >> i); + fee_rates + .zip(self.bins.iter().copied()) + .skip_while(|(_fee_rate, vsize)| *vsize == 0) + .try_for_each(|element| seq.serialize_element(&element))?; seq.end() } } + +#[cfg(test)] +mod tests { + use super::Histogram; + use bitcoin::Amount; + use serde_json::json; + + #[test] + fn test_histogram() { + let items = vec![ + (Amount::from_sat(20), 10), + (Amount::from_sat(10), 10), + (Amount::from_sat(60), 10), + (Amount::from_sat(30), 10), + (Amount::from_sat(70), 10), + (Amount::from_sat(50), 10), + (Amount::from_sat(40), 10), + (Amount::from_sat(80), 10), + ]; + let hist = json!(Histogram::new(items.into_iter())); + assert_eq!(hist, json!([[15, 10], [7, 40], [3, 20], [1, 10]])); + } +} From 2609caaebce5205bfe9916e885f728c611a1fee9 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 21 May 2021 22:05:26 +0300 Subject: [PATCH 014/113] Ignore mempool in sync.sh --- sync.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync.sh b/sync.sh index de14906..00efe04 100755 --- a/sync.sh +++ b/sync.sh @@ -10,6 +10,6 @@ shift CMD="target/release/sync --network $NETWORK --db-dir ./db2 --daemon-dir $HOME/.bitcoin" export RUST_LOG=${RUST_LOG-info} -$CMD $* +$CMD --ignore-mempool $* # use SIGINT to quit From d4559a31e6aa592db826503db84fd99f6d83c8ec Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 21 May 2021 13:56:04 +0300 Subject: [PATCH 015/113] Add initial support for JSON-RPC batching --- contrib/client.py | 9 ++++++--- contrib/tx_fee.py | 11 ++++++++--- src/electrum.rs | 21 ++++++++++++++++++++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/contrib/client.py b/contrib/client.py index 145d728..26bbf24 100644 --- a/contrib/client.py +++ b/contrib/client.py @@ -7,13 +7,16 @@ class Client: self.f = self.s.makefile('r') self.id = 0 - def call(self, method, *args): - req = { + def request(self, method, *args): + self.id += 1 + return { 'id': self.id, 'method': method, 'params': list(args), 'jsonrpc': '2.0', } - msg = json.dumps(req) + '\n' + + def call(self, *requests): + msg = json.dumps(requests) + '\n' self.s.sendall(msg.encode('ascii')) return json.loads(self.f.readline()) diff --git a/contrib/tx_fee.py b/contrib/tx_fee.py index 05ae2fb..c8d69f2 100755 --- a/contrib/tx_fee.py +++ b/contrib/tx_fee.py @@ -8,13 +8,18 @@ def main(): args = parser.parse_args() conn = client.Client(("localhost", 50001)) - tx = conn.call("blockchain.transaction.get", args.txid, True)["result"] - fee = 0 + tx = conn.call(conn.request("blockchain.transaction.get", args.txid, True))[0]["result"] + requests = [] for vin in tx["vin"]: prev_txid = vin["txid"] - prev_tx = conn.call("blockchain.transaction.get", prev_txid, True)["result"] + requests.append(conn.request("blockchain.transaction.get", prev_txid, True)) + + fee = 0 + for vin, response in zip(tx["vin"], conn.call(*requests)): + prev_tx = response["result"] txo = prev_tx["vout"][vin["vout"]] fee += txo["value"] + fee -= sum(vout["value"] for vout in tx["vout"]) print(f'vSize = {tx["vsize"]}, Fee = {1e3 * fee:.2f} mBTC = {1e8 * fee / tx["vsize"]:.2f} sat/vB') diff --git a/src/electrum.rs b/src/electrum.rs index 6754813..3cf89e5 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -38,6 +38,13 @@ struct Request { params: Value, } +#[derive(Deserialize)] +#[serde(untagged)] +enum Requests { + Single(Request), + Batch(Vec), +} + #[derive(Deserialize, Debug, PartialEq, Eq)] #[serde(untagged)] enum Version { @@ -127,12 +134,24 @@ impl Rpc { } pub fn handle_request(&self, client: &mut Client, value: Value) -> Result { + let requests: Requests = from_value(value).context("invalid request")?; + match requests { + Requests::Single(request) => self.handle_single_request(client, request), + Requests::Batch(requests) => requests + .into_iter() + .map(|request| self.handle_single_request(client, request)) + .collect::>>() + .map(|results| json!(results)), + } + } + + fn handle_single_request(&self, client: &mut Client, request: Request) -> Result { let Request { id, jsonrpc, method, params, - } = from_value(value).context("invalid request")?; + } = request; self.rpc_duration.observe_duration(&method, || { let result = match method.as_str() { "blockchain.scripthash.get_history" => { From 8a39d47092b53648cec0646944ac6cba2fe2facb Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 24 May 2021 17:32:10 +0300 Subject: [PATCH 016/113] Fix contrib/get_tip.py --- contrib/get_tip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/get_tip.py b/contrib/get_tip.py index bb2b517..7ff8ac7 100755 --- a/contrib/get_tip.py +++ b/contrib/get_tip.py @@ -10,7 +10,7 @@ def main(): args = parser.parse_args() conn = client.Client((args.host, args.port)) - print(json.dumps(conn.call("blockchain.headers.subscribe")["result"])) + print(conn.call(conn.request("blockchain.headers.subscribe"))[0]["result"]) if __name__ == '__main__': main() From f4102dca8670de8460836e39e3f578722cee58e9 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 25 May 2021 18:09:32 +0300 Subject: [PATCH 017/113] Drop peers on notification sending errors --- src/server.rs | 63 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/server.rs b/src/server.rs index 9cb5b3b..dc17450 100644 --- a/src/server.rs +++ b/src/server.rs @@ -35,19 +35,27 @@ where } struct Peer { + id: usize, client: Client, stream: TcpStream, } impl Peer { - fn new(stream: TcpStream) -> Self { + fn new(id: usize, stream: TcpStream) -> Self { Self { + id, client: Client::default(), stream, } } } +impl Drop for Peer { + fn drop(&mut self) { + let _ = self.stream.shutdown(Shutdown::Both); + } +} + fn tip_receiver(config: &Config) -> Result> { let (tip_tx, tip_rx) = bounded(0); let rpc = rpc_connect(&config)?; @@ -92,23 +100,40 @@ pub fn run(config: &Config, mut rpc: Rpc) -> Result<()> { let event = event.context("server disconnected")?; let buffered_events = server_rx.iter().take(server_rx.len()); for event in std::iter::once(event).chain(buffered_events) { - handle(&rpc, &mut peers, event); + handle_event(&rpc, &mut peers, event); } }, }; rpc.sync().context("rpc sync failed")?; - peers - .par_iter_mut() - .map(|(peer_id, peer)| { - let notifications = rpc.update_client(&mut peer.client)?; - send(*peer_id, peer, ¬ifications) - }) - .collect::>()?; + peers = notify_peers(&rpc, peers); } info!("stopping Electrum RPC server"); Ok(()) } +fn notify_peers(rpc: &Rpc, peers: HashMap) -> HashMap { + // peers are dropped and disconnected on error. + peers + .into_par_iter() + .filter_map(|(peer_id, peer)| match notify_peer(&rpc, peer) { + Ok(peer) => Some((peer.id, peer)), + Err(e) => { + error!("failed to notify peer {}: {}", peer_id, e); + None + } + }) + .collect() +} + +fn notify_peer(rpc: &Rpc, mut peer: Peer) -> Result { + // peer is dropped and disconnected on error. + let notifications = rpc + .update_client(&mut peer.client) + .context("failed to generate notifications")?; + send_to_peer(&mut peer, ¬ifications).context("failed to send notifications")?; + Ok(peer) +} + struct Event { peer_id: usize, msg: Message, @@ -120,25 +145,23 @@ enum Message { Done, } -fn handle(rpc: &Rpc, peers: &mut HashMap, event: Event) { +fn handle_event(rpc: &Rpc, peers: &mut HashMap, event: Event) { match event.msg { Message::New(stream) => { debug!("{}: connected", event.peer_id); - peers.insert(event.peer_id, Peer::new(stream)); + peers.insert(event.peer_id, Peer::new(event.peer_id, stream)); } Message::Request(line) => { let result = match peers.get_mut(&event.peer_id) { - Some(peer) => handle_request(rpc, event.peer_id, peer, line), + Some(peer) => handle_request(rpc, peer, line), None => { warn!("{}: unknown peer for {}", event.peer_id, line); Ok(()) } }; if let Err(e) = result { - error!("{}: {}", event.peer_id, e); - let _ = peers - .remove(&event.peer_id) - .map(|peer| peer.stream.shutdown(Shutdown::Both)); + error!("{}: disconnecting due to {}", event.peer_id, e); + drop(peers.remove(&event.peer_id)); // disconnect peer due to error } } Message::Done => { @@ -148,18 +171,18 @@ fn handle(rpc: &Rpc, peers: &mut HashMap, event: Event) { } } -fn handle_request(rpc: &Rpc, peer_id: usize, peer: &mut Peer, line: String) -> Result<()> { +fn handle_request(rpc: &Rpc, peer: &mut Peer, line: String) -> Result<()> { let request: Value = from_str(&line).with_context(|| format!("invalid request: {}", line))?; let response: Value = rpc .handle_request(&mut peer.client, request) .with_context(|| format!("failed to handle request: {}", line))?; - send(peer_id, peer, &[response]) + send_to_peer(peer, &[response]) } -fn send(peer_id: usize, peer: &mut Peer, values: &[Value]) -> Result<()> { +fn send_to_peer(peer: &mut Peer, values: &[Value]) -> Result<()> { for value in values { let mut response = value.to_string(); - debug!("{}: send {}", peer_id, response); + debug!("{}: send {}", peer.id, response); response += "\n"; peer.stream .write_all(response.as_bytes()) From 1d4ab4f325a99f256529cfa1d55424b9b6aba0b5 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 26 May 2021 15:18:55 +0300 Subject: [PATCH 018/113] Remove unused configuration parameters --- internal/config_specification.toml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/internal/config_specification.toml b/internal/config_specification.toml index d69c2cc..15ba95d 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -34,14 +34,6 @@ name = "blocks_dir" type = "std::path::PathBuf" doc = "Analogous to bitcoind's -blocksdir option, this specifies the directory containing the raw blocks files (blk*.dat)" -[[param]] -name = "cookie" -type = "String" -doc = "DEPRECATED: use cookie_file or auth instead!" -# Force the user to use config file in order to avoid password leaks -argument = false -env_var = false - [[param]] name = "auth" type = "String" @@ -102,18 +94,6 @@ default = "10" name = "ignore_mempool" doc = "Don't sync mempool - queries will show only confirmed transactions." -[[param]] -name = "tx_cache_size_mb" -type = "f32" -doc = "Total size of transactions to cache (MB)" -default = "10.0" - -[[param]] -name = "blocktxids_cache_size_mb" -type = "f32" -doc = "Total size of block transactions IDs to cache (in MB)" -default = "10.0" - [[param]] name = "txid_limit" type = "usize" From 52443f370fa275976e1903847a1f75b620090414 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 26 May 2021 15:21:32 +0300 Subject: [PATCH 019/113] Include blockhash on missing txid for merkle proof --- src/electrum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electrum.rs b/src/electrum.rs index 3cf89e5..023f415 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -309,7 +309,7 @@ impl Rpc { debug!("txids cache miss: {}", blockhash); let txids = self.daemon.get_block_txids(blockhash)?; match txids.iter().position(|current_txid| *current_txid == txid) { - None => bail!("missing tx {} for merkle proof", txid), + None => bail!("missing txid {} in block {}", txid, blockhash), Some(position) => Ok(proof_to_value(&Proof::create(&txids, position))), } } From d4ef9a38606d00e3d5426b453da886c6b4aa5ad8 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 26 May 2021 22:00:10 +0300 Subject: [PATCH 020/113] Disconnect peers implicitly when closing the server Otherwise, recv_loop fails to send Message::Done (due to disconnected channel). --- src/server.rs | 70 +++++++++++++++++++++------------------------------ 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/src/server.rs b/src/server.rs index dc17450..e415c9e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -42,26 +42,20 @@ struct Peer { impl Peer { fn new(id: usize, stream: TcpStream) -> Self { - Self { - id, - client: Client::default(), - stream, - } + let client = Client::default(); + Self { id, client, stream } } -} -impl Drop for Peer { - fn drop(&mut self) { + fn disconnect(self) { let _ = self.stream.shutdown(Shutdown::Both); } } fn tip_receiver(config: &Config) -> Result> { + let duration = u64::try_from(config.wait_duration.as_millis()).unwrap(); let (tip_tx, tip_rx) = bounded(0); let rpc = rpc_connect(&config)?; - let duration = u64::try_from(config.wait_duration.as_millis()).unwrap(); - use crossbeam_channel::TrySendError; spawn("tip_loop", move || loop { let tip = rpc.get_best_block_hash()?; @@ -105,33 +99,31 @@ pub fn run(config: &Config, mut rpc: Rpc) -> Result<()> { }, }; rpc.sync().context("rpc sync failed")?; - peers = notify_peers(&rpc, peers); + peers = notify_peers(&rpc, peers); // peers are disconnected on error. } info!("stopping Electrum RPC server"); Ok(()) } fn notify_peers(rpc: &Rpc, peers: HashMap) -> HashMap { - // peers are dropped and disconnected on error. peers .into_par_iter() - .filter_map(|(peer_id, peer)| match notify_peer(&rpc, peer) { - Ok(peer) => Some((peer.id, peer)), + .filter_map(|(_, mut peer)| match notify_peer(&rpc, &mut peer) { + Ok(()) => Some((peer.id, peer)), Err(e) => { - error!("failed to notify peer {}: {}", peer_id, e); + error!("failed to notify peer {}: {}", peer.id, e); + peer.disconnect(); None } }) .collect() } -fn notify_peer(rpc: &Rpc, mut peer: Peer) -> Result { - // peer is dropped and disconnected on error. +fn notify_peer(rpc: &Rpc, peer: &mut Peer) -> Result<()> { let notifications = rpc .update_client(&mut peer.client) .context("failed to generate notifications")?; - send_to_peer(&mut peer, ¬ifications).context("failed to send notifications")?; - Ok(peer) + send_to_peer(peer, ¬ifications).context("failed to send notifications") } struct Event { @@ -146,27 +138,25 @@ enum Message { } fn handle_event(rpc: &Rpc, peers: &mut HashMap, event: Event) { - match event.msg { + let Event { msg, peer_id } = event; + match msg { Message::New(stream) => { - debug!("{}: connected", event.peer_id); - peers.insert(event.peer_id, Peer::new(event.peer_id, stream)); + debug!("{}: connected", peer_id); + peers.insert(peer_id, Peer::new(peer_id, stream)); } Message::Request(line) => { - let result = match peers.get_mut(&event.peer_id) { + let result = match peers.get_mut(&peer_id) { Some(peer) => handle_request(rpc, peer, line), - None => { - warn!("{}: unknown peer for {}", event.peer_id, line); - Ok(()) - } + None => return, // unknown peer }; if let Err(e) = result { - error!("{}: disconnecting due to {}", event.peer_id, e); - drop(peers.remove(&event.peer_id)); // disconnect peer due to error + error!("{}: disconnecting due to {}", peer_id, e); + peers.remove(&peer_id).unwrap().disconnect(); } } Message::Done => { - debug!("{}: disconnected", event.peer_id); - peers.remove(&event.peer_id); + // already disconnected, just remove from peers' map + peers.remove(&peer_id); } } } @@ -205,20 +195,18 @@ fn accept_loop(listener: TcpListener, server_tx: Sender) -> Result<()> { } fn recv_loop(peer_id: usize, stream: &TcpStream, server_tx: Sender) -> Result<()> { - server_tx.send(Event { - peer_id, - msg: Message::New(stream.try_clone()?), - })?; - let reader = BufReader::new(stream); - for line in reader.lines() { + let msg = Message::New(stream.try_clone()?); + server_tx.send(Event { peer_id, msg })?; + + for line in BufReader::new(stream).lines() { let line = line.with_context(|| format!("{}: recv failed", peer_id))?; debug!("{}: recv {}", peer_id, line); let msg = Message::Request(line); server_tx.send(Event { peer_id, msg })?; } - server_tx.send(Event { - peer_id, - msg: Message::Done, - })?; + + debug!("{}: disconnected", peer_id); + let msg = Message::Done; + server_tx.send(Event { peer_id, msg })?; Ok(()) } From b0deaecdc48f652d5ad8b8d768e278e5634362a7 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 27 May 2021 20:44:51 +0300 Subject: [PATCH 021/113] Allow optional metrics collection Can be disable by `--no-default-features`. Also, enable RocksDB ZSTD compression (removing the feature). --- Cargo.lock | 7 ++ Cargo.toml | 7 +- src/config.rs | 7 ++ src/electrum.rs | 9 +-- src/index.rs | 14 ++-- src/metrics.rs | 190 +++++++++++++++++++++++++++++------------------- 6 files changed, 144 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfd646a..7ff49fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -720,9 +720,16 @@ dependencies = [ "memchr", "parking_lot", "procfs", + "protobuf", "thiserror", ] +[[package]] +name = "protobuf" +version = "2.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45604fc7a88158e7d514d8e22e14ac746081e7a70d7690074dd0029ee37458d6" + [[package]] name = "quick-error" version = "1.2.3" diff --git a/Cargo.toml b/Cargo.toml index f559ee5..7a6208e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,8 @@ edition = "2018" build = "build.rs" [features] -default = ["rocksdb/zstd", "prometheus/process"] +default = ["metrics"] +metrics = ["prometheus"] [package.metadata.configure_me] spec = "internal/config_specification.toml" @@ -28,9 +29,9 @@ dirs-next = "2.0" env_logger = "0.7" hyper = "0.10" log = "0.4" -prometheus = { version = "0.12", default-features = false } +prometheus = { version = "0.12", features = ["process"], optional = true } rayon = "1.5" -rocksdb = { git = "https://github.com/romanz/rust-rocksdb", rev = "4554d19b2ff2e34493564b4d868454097c74b693", default-features = false } # to support building with Rust 1.41.1 and https://github.com/romanz/electrs/issues/403 +rocksdb = { git = "https://github.com/romanz/rust-rocksdb", rev = "4554d19b2ff2e34493564b4d868454097c74b693", features = ["zstd"] } # to support building with Rust 1.41.1 and https://github.com/romanz/electrs/issues/403 rust-crypto = "0.2" serde = "1.0" serde_derive = "1.0" diff --git a/src/config.rs b/src/config.rs index 950d762..9ba205e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -208,6 +208,13 @@ impl Config { (DEFAULT_SERVER_ADDRESS, default_electrum_port).into(), ResolvAddr::resolve_or_exit, ); + #[cfg(not(feature = "metrics"))] + { + assert!( + config.monitoring_addr.is_none(), + "Enable \"metrics\" feature to specify monitoring_addr" + ); + } let monitoring_addr: SocketAddr = config.monitoring_addr.map_or( (DEFAULT_SERVER_ADDRESS, default_monitoring_port).into(), ResolvAddr::resolve_or_exit, diff --git a/src/electrum.rs b/src/electrum.rs index 023f415..c921bac 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -79,11 +79,10 @@ pub struct Rpc { impl Rpc { pub fn new(config: &Config, tracker: Tracker) -> Result { - let rpc_duration = tracker.metrics().histogram_vec( - "rpc_duration", - "RPC duration (in seconds)", - &["method"], - ); + let rpc_duration = + tracker + .metrics() + .histogram_vec("rpc_duration", "RPC duration (in seconds)", "method"); Ok(Self { tracker, cache: Cache::default(), diff --git a/src/index.rs b/src/index.rs index 66b47e4..8f1c3a0 100644 --- a/src/index.rs +++ b/src/index.rs @@ -25,30 +25,30 @@ impl Stats { update_duration: metrics.histogram_vec( "index_update_duration", "Index update duration (in seconds)", - &["step"], + "step", ), update_size: metrics.histogram_vec( "index_update_size", "Index update size (in bytes)", - &["step"], + "step", ), lookup_duration: metrics.histogram_vec( "index_lookup_duration", "Index lookup duration (in seconds)", - &["step"], + "step", ), } } fn report_stats(&self, batch: &db::WriteBatch) { self.update_size - .observe_size("write_funding_rows", db_rows_size(&batch.funding_rows)); + .observe("write_funding_rows", db_rows_size(&batch.funding_rows)); self.update_size - .observe_size("write_spending_rows", db_rows_size(&batch.spending_rows)); + .observe("write_spending_rows", db_rows_size(&batch.spending_rows)); self.update_size - .observe_size("write_txid_rows", db_rows_size(&batch.txid_rows)); + .observe("write_txid_rows", db_rows_size(&batch.txid_rows)); self.update_size - .observe_size("write_header_rows", db_rows_size(&batch.header_rows)); + .observe("write_header_rows", db_rows_size(&batch.header_rows)); debug!( "writing {} funding and {} spending rows from {} transactions, {} blocks", batch.funding_rows.len(), diff --git a/src/metrics.rs b/src/metrics.rs index 9871a8e..102c4f7 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -1,89 +1,129 @@ -use anyhow::{Context, Result}; -use hyper::server::{Handler, Listening, Request, Response, Server}; -use prometheus::{self, Encoder, HistogramOpts, HistogramVec, Registry}; +#[cfg(feature = "metrics")] +mod metrics_impl { + use anyhow::{Context, Result}; + use hyper::server::{Handler, Listening, Request, Response, Server}; + use prometheus::process_collector::ProcessCollector; + use prometheus::{self, Encoder, HistogramOpts, HistogramVec, Registry}; -#[cfg(feature = "process_collector")] -use prometheus::process_collector::ProcessCollector; + use std::net::SocketAddr; -use std::net::SocketAddr; + pub struct Metrics { + reg: Registry, + listen: Listening, + } -pub struct Metrics { - reg: Registry, - listen: Listening, -} + impl Metrics { + pub fn new(addr: SocketAddr) -> Result { + let reg = Registry::new(); -impl Drop for Metrics { - fn drop(&mut self) { - debug!("closing Prometheus server"); - if let Err(e) = self.listen.close() { - warn!("failed to stop Prometheus server: {}", e); + reg.register(Box::new(ProcessCollector::for_self())) + .expect("failed to register ProcessCollector"); + + let listen = Server::http(addr)? + .handle(RegistryHandler { reg: reg.clone() }) + .with_context(|| format!("failed to serve on {}", addr))?; + info!("serving Prometheus metrics on {}", addr); + Ok(Self { reg, listen }) + } + + pub fn histogram_vec(&self, name: &str, desc: &str, label: &str) -> Histogram { + let opts = HistogramOpts::new(name, desc); + let hist = HistogramVec::new(opts, &[label]).unwrap(); + self.reg + .register(Box::new(hist.clone())) + .expect("failed to register Histogram"); + Histogram { hist } + } + } + + impl Drop for Metrics { + fn drop(&mut self) { + debug!("closing Prometheus server"); + if let Err(e) = self.listen.close() { + warn!("failed to stop Prometheus server: {}", e); + } + } + } + + #[derive(Clone)] + pub struct Histogram { + hist: HistogramVec, + } + + impl Histogram { + pub fn observe(&self, label: &str, value: usize) { + self.hist.with_label_values(&[label]).observe(value as f64); + } + + pub fn observe_duration(&self, label: &str, func: F) -> T + where + F: FnOnce() -> T, + { + self.hist + .with_label_values(&[label]) + .observe_closure_duration(func) + } + } + + struct RegistryHandler { + reg: Registry, + } + + impl RegistryHandler { + fn gather(&self) -> Result> { + let mut buffer = vec![]; + prometheus::TextEncoder::new() + .encode(&self.reg.gather(), &mut buffer) + .context("failed to encode metrics")?; + Ok(buffer) + } + } + + impl Handler for RegistryHandler { + fn handle(&self, req: Request, res: Response) { + trace!("{} {}", req.method, req.uri); + let buffer = self.gather().expect("failed to gather metrics"); + res.send(&buffer).expect("failed to send metrics"); } } } -#[derive(Clone)] -pub struct Histogram { - hist: HistogramVec, -} +#[cfg(feature = "metrics")] +pub use metrics_impl::{Histogram, Metrics}; -impl Histogram { - pub fn observe_size(&self, label: &str, value: usize) { - self.hist.with_label_values(&[label]).observe(value as f64); +#[cfg(not(feature = "metrics"))] +mod metrics_fake { + use anyhow::Result; + + use std::net::SocketAddr; + + pub struct Metrics {} + + impl Metrics { + pub fn new(_addr: SocketAddr) -> Result { + debug!("metrics collection is disabled"); + Ok(Self {}) + } + + pub fn histogram_vec(&self, _name: &str, _desc: &str, _label: &str) -> Histogram { + Histogram {} + } } - pub fn observe_duration(&self, label: &str, func: F) -> T - where - F: FnOnce() -> T, - { - self.hist - .with_label_values(&[label]) - .observe_closure_duration(func) + #[derive(Clone)] + pub struct Histogram {} + + impl Histogram { + pub fn observe(&self, _label: &str, _value: usize) {} + + pub fn observe_duration(&self, _label: &str, func: F) -> T + where + F: FnOnce() -> T, + { + func() + } } } -struct RegistryHandler { - reg: Registry, -} - -impl RegistryHandler { - fn gather(&self) -> Result> { - let mut buffer = vec![]; - prometheus::TextEncoder::new() - .encode(&self.reg.gather(), &mut buffer) - .context("failed to encode metrics")?; - Ok(buffer) - } -} - -impl Handler for RegistryHandler { - fn handle(&self, req: Request, res: Response) { - trace!("{} {}", req.method, req.uri); - let buffer = self.gather().expect("failed to gather metrics"); - res.send(&buffer).expect("send failed"); - } -} - -impl Metrics { - pub fn new(addr: SocketAddr) -> Result { - let reg = Registry::new(); - - #[cfg(feature = "prometheus/process")] - reg.register(Box::new(ProcessCollector::for_self())) - .expect("failed to register ProcessCollector"); - - let listen = Server::http(addr)? - .handle(RegistryHandler { reg: reg.clone() }) - .with_context(|| format!("failed to serve on {}", addr))?; - info!("serving Prometheus metrics on {}", addr); - Ok(Self { reg, listen }) - } - - pub fn histogram_vec(&self, name: &str, desc: &str, labels: &[&str]) -> Histogram { - let opts = HistogramOpts::new(name, desc); - let hist = HistogramVec::new(opts, labels).unwrap(); - self.reg - .register(Box::new(hist.clone())) - .expect("failed to register Histogram"); - Histogram { hist } - } -} +#[cfg(not(feature = "metrics"))] +pub use metrics_fake::{Histogram, Metrics}; From 7a200480a4dae82098b6eed2e125aaf91fcbd35e Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 29 May 2021 21:27:53 +0300 Subject: [PATCH 022/113] Refactor RPC connection initialization Also, wait for IBD to finish. --- src/daemon.rs | 65 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 305fa9e..1ec727f 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -85,42 +85,57 @@ impl Connection { } } +enum PollResult { + Done(Result<()>), + Retry, +} + +fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { + use bitcoincore_rpc::{ + jsonrpc::error::Error::Rpc as ServerError, Error::JsonRpc as JsonRpcError, + }; + + match client.get_blockchain_info() { + Ok(info) => { + let left_blocks = info.headers - info.blocks; + if info.initial_block_download { + info!("waiting for IBD to finish: {} blocks left", left_blocks); + return PollResult::Retry; + } + if left_blocks > 0 { + info!("waiting for {} blocks to download", left_blocks); + return PollResult::Retry; + } + return PollResult::Done(Ok(())); + } + Err(err) => { + if let JsonRpcError(ServerError(ref e)) = err { + if e.code == -28 { + info!("waiting for RPC warmup: {}", e.message); + return PollResult::Retry; + } + } + return PollResult::Done(Err(err).context("daemon not available")); + } + } +} + pub(crate) fn rpc_connect(config: &Config) -> Result { let rpc_url = format!("http://{}", config.daemon_rpc_addr); if !config.daemon_cookie_file.exists() { bail!("{:?} is missing", config.daemon_cookie_file); } let rpc_auth = bitcoincore_rpc::Auth::CookieFile(config.daemon_cookie_file.clone()); - let rpc = bitcoincore_rpc::Client::new(rpc_url, rpc_auth) + let mut client = bitcoincore_rpc::Client::new(rpc_url, rpc_auth) .with_context(|| format!("failed to connect to RPC: {}", config.daemon_rpc_addr))?; - use bitcoincore_rpc::{ - jsonrpc::error::Error::Rpc as ServerError, Error::JsonRpc as JsonRpcError, - }; loop { - match rpc.get_blockchain_info() { - Ok(info) => { - if info.blocks < info.headers { - info!( - "waiting for {} blocks to download", - info.headers - info.blocks - ); - std::thread::sleep(std::time::Duration::from_secs(1)); - continue; - } - } - Err(err) => { - if let JsonRpcError(ServerError(ref e)) = err { - if e.code == -28 { - info!("waiting for RPC warmup: {}", e.message); - std::thread::sleep(std::time::Duration::from_secs(1)); - continue; - } - } - return Err(err).context("daemon not available"); + match rpc_poll(&mut client) { + PollResult::Done(result) => return result.map(|()| client), + PollResult::Retry => { + std::thread::sleep(std::time::Duration::from_secs(1)); // wait a bit before polling } } - return Ok(rpc); } } From cca0f270c2f47d6e3664a501745ee15df350f68d Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 30 May 2021 09:29:09 +0300 Subject: [PATCH 023/113] Run sync in a loop --- src/bin/sync.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bin/sync.rs b/src/bin/sync.rs index ef91958..318e354 100644 --- a/src/bin/sync.rs +++ b/src/bin/sync.rs @@ -5,5 +5,9 @@ use electrs::{Config, Daemon, Tracker}; fn main() -> Result<()> { let config = Config::from_args(); let daemon = Daemon::connect(&config)?; - Tracker::new(&config)?.sync(&daemon) + let mut tracker = Tracker::new(&config)?; + loop { + tracker.sync(&daemon)?; + std::thread::sleep(config.wait_duration); + } } From 15ec11f8b6b915df98614f207d21b5031897ce18 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 30 May 2021 21:38:51 +0300 Subject: [PATCH 024/113] Disable default features for rocksdb dependency --- Cargo.toml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7a6208e..9d4ffed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,12 +31,18 @@ hyper = "0.10" log = "0.4" prometheus = { version = "0.12", features = ["process"], optional = true } rayon = "1.5" -rocksdb = { git = "https://github.com/romanz/rust-rocksdb", rev = "4554d19b2ff2e34493564b4d868454097c74b693", features = ["zstd"] } # to support building with Rust 1.41.1 and https://github.com/romanz/electrs/issues/403 rust-crypto = "0.2" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" signal-hook = "0.3" +[dependencies.rocksdb] +# support building with Rust 1.41.1 and workaround https://github.com/romanz/electrs/issues/403 +git = "https://github.com/romanz/rust-rocksdb" +rev = "4554d19b2ff2e34493564b4d868454097c74b693" +default-features = false +features = ["zstd"] + [build-dependencies] -configure_me_codegen = "0.4" \ No newline at end of file +configure_me_codegen = "0.4" From 2df789e0537067d010c8271d9200c85ba11664f5 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 30 May 2021 21:42:17 +0300 Subject: [PATCH 025/113] Don't panic on incorrect metrics' configuration --- src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9ba205e..5ca103f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -210,10 +210,10 @@ impl Config { ); #[cfg(not(feature = "metrics"))] { - assert!( - config.monitoring_addr.is_none(), - "Enable \"metrics\" feature to specify monitoring_addr" - ); + if config.monitoring_addr.is_some() { + eprintln!("Error: enable \"metrics\" feature to specify monitoring_addr"); + std::process::exit(1); + } } let monitoring_addr: SocketAddr = config.monitoring_addr.map_or( (DEFAULT_SERVER_ADDRESS, default_monitoring_port).into(), From df513cfb18297d3d76097bb62dba93676bbf9a94 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 31 May 2021 16:25:55 +0300 Subject: [PATCH 026/113] Don't ignore --cookie-file configuration parameter Fixes #407 --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 5ca103f..cc58fb6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -228,7 +228,7 @@ impl Config { } let daemon_dir = &config.daemon_dir; - let daemon_cookie_file = daemon_dir.join(".cookie"); + let daemon_cookie_file = config.cookie_file.unwrap_or(daemon_dir.join(".cookie")); let config = Config { network: config.network, From cf078acbc6e17c84d40fbef6adcb490032211975 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 31 May 2021 18:47:26 +0300 Subject: [PATCH 027/113] Fixup clippy warnings --- src/config.rs | 4 +++- src/daemon.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index cc58fb6..41a81a7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -228,7 +228,9 @@ impl Config { } let daemon_dir = &config.daemon_dir; - let daemon_cookie_file = config.cookie_file.unwrap_or(daemon_dir.join(".cookie")); + let daemon_cookie_file = config + .cookie_file + .unwrap_or_else(|| daemon_dir.join(".cookie")); let config = Config { network: config.network, diff --git a/src/daemon.rs b/src/daemon.rs index 1ec727f..b46f0d9 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -106,7 +106,7 @@ fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { info!("waiting for {} blocks to download", left_blocks); return PollResult::Retry; } - return PollResult::Done(Ok(())); + PollResult::Done(Ok(())) } Err(err) => { if let JsonRpcError(ServerError(ref e)) = err { @@ -115,7 +115,7 @@ fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { return PollResult::Retry; } } - return PollResult::Done(Err(err).context("daemon not available")); + PollResult::Done(Err(err).context("daemon not available")) } } } From 8bb6d2ab206da933f924effc80d880fbc30d3a96 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 31 May 2021 21:26:35 +0300 Subject: [PATCH 028/113] Drop rust-crypto unneeded dependency --- Cargo.lock | 51 +-------------------------------------------------- Cargo.toml | 1 - 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ff49fd..afa8d4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -325,7 +325,6 @@ dependencies = [ "prometheus", "rayon", "rocksdb", - "rust-crypto", "serde", "serde_derive", "serde_json", @@ -375,12 +374,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "getrandom" version = "0.2.3" @@ -745,29 +738,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.6.5" @@ -959,31 +929,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33e4fb37ba46888052c763e4ec2acfedd8f00f62897b630cadb6298b833675e" -[[package]] -name = "rust-crypto" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" -dependencies = [ - "gcc", - "libc", - "rand 0.3.23", - "rustc-serialize", - "time", -] - [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-serialize" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" - [[package]] name = "ryu" version = "1.0.5" @@ -1008,7 +959,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee5070fdc6f26ca5be6dcfc3d07c76fdb974a63a8b246b459854274145f5a258" dependencies = [ - "rand 0.6.5", + "rand", "secp256k1-sys", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 9d4ffed..ad678fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,6 @@ hyper = "0.10" log = "0.4" prometheus = { version = "0.12", features = ["process"], optional = true } rayon = "1.5" -rust-crypto = "0.2" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" From 4a09f8ac8bc1031105e0e1e3d95f6e2855f4b0f3 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 31 May 2021 21:35:48 +0300 Subject: [PATCH 029/113] Update dependencies --- Cargo.lock | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afa8d4f..0410539 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" dependencies = [ "jobserver", ] @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -272,11 +272,10 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "autocfg 1.0.1", "cfg-if 1.0.0", "lazy_static", ] @@ -513,9 +512,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.94" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" [[package]] name = "libloading" @@ -588,9 +587,9 @@ checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "memoffset" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" dependencies = [ "autocfg 1.0.1", ] @@ -1057,18 +1056,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" dependencies = [ "proc-macro2", "quote", @@ -1141,9 +1140,9 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +checksum = "33717dca7ac877f497014e10d73f3acf948c342bee31b5ca7892faf94ccc6b49" dependencies = [ "tinyvec", ] From 50e55aa818d3a4c24b2d0ee605bc02f629c50328 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 2 Jun 2021 12:54:03 +0300 Subject: [PATCH 030/113] Refactor status helper functions --- src/status.rs | 60 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/status.rs b/src/status.rs index 2e78742..6bca9de 100644 --- a/src/status.rs +++ b/src/status.rs @@ -106,32 +106,6 @@ impl Status { } } - fn filter_outputs(&self, tx: &Transaction) -> Vec { - let outputs = tx.output.iter().zip(0u32..); - outputs - .filter_map(move |(txo, vout)| { - if ScriptHash::new(&txo.script_pubkey) == self.scripthash { - Some(vout) - } else { - None - } - }) - .collect() - } - - fn filter_inputs(&self, tx: &Transaction, outpoints: &HashSet) -> Vec { - tx.input - .iter() - .filter_map(|txi| { - if outpoints.contains(&txi.previous_output) { - Some(txi.previous_output) - } else { - None - } - }) - .collect() - } - fn funding_confirmed(&self, chain: &Chain) -> HashSet { self.confirmed .iter() @@ -230,7 +204,7 @@ impl Status { self.for_new_blocks(funding_blockhashes, daemon, |blockhash, block| { let txids: Vec = block.txdata.iter().map(|tx| tx.txid()).collect(); for (pos, (tx, txid)) in block.txdata.into_iter().zip(txids.iter()).enumerate() { - let funding_outputs = self.filter_outputs(&tx); + let funding_outputs = filter_outputs(&tx, &self.scripthash); if funding_outputs.is_empty() { continue; } @@ -255,7 +229,7 @@ impl Status { |blockhash, block| { let txids: Vec = block.txdata.iter().map(|tx| tx.txid()).collect(); for (pos, (tx, txid)) in block.txdata.into_iter().zip(txids.iter()).enumerate() { - let spent_outpoints = self.filter_inputs(&tx, &outpoints); + let spent_outpoints = filter_inputs(&tx, &outpoints); if spent_outpoints.is_empty() { continue; } @@ -293,7 +267,7 @@ impl Status { ) -> Vec { let mut result = HashMap::::new(); for entry in mempool.filter_by_funding(&self.scripthash) { - let funding_outputs = self.filter_outputs(&entry.tx); + let funding_outputs = filter_outputs(&entry.tx, &self.scripthash); assert!(!funding_outputs.is_empty()); outpoints.extend(make_outpoints(&entry.txid, &funding_outputs)); result.entry(entry.txid).or_default().outputs = funding_outputs; @@ -303,7 +277,7 @@ impl Status { .iter() .flat_map(|outpoint| mempool.filter_by_spending(outpoint)) { - let spent_outpoints = self.filter_inputs(&entry.tx, &outpoints); + let spent_outpoints = filter_inputs(&entry.tx, &outpoints); assert!(!spent_outpoints.is_empty()); result.entry(entry.txid).or_default().spent = spent_outpoints; cache.add_tx(entry.txid, || entry.tx.clone()); @@ -364,3 +338,29 @@ impl Status { self.statushash } } + +fn filter_outputs(tx: &Transaction, scripthash: &ScriptHash) -> Vec { + let outputs = tx.output.iter().zip(0u32..); + outputs + .filter_map(move |(txo, vout)| { + if ScriptHash::new(&txo.script_pubkey) == *scripthash { + Some(vout) + } else { + None + } + }) + .collect() +} + +fn filter_inputs(tx: &Transaction, outpoints: &HashSet) -> Vec { + tx.input + .iter() + .filter_map(|txi| { + if outpoints.contains(&txi.previous_output) { + Some(txi.previous_output) + } else { + None + } + }) + .collect() +} From 6eb8eb5227b8cd3ba882673dfd40b838e8f3f14e Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 3 Jun 2021 19:08:23 +0300 Subject: [PATCH 031/113] Refactor waiting for daemon --- src/daemon.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index b46f0d9..ac32f5a 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -98,12 +98,16 @@ fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { match client.get_blockchain_info() { Ok(info) => { let left_blocks = info.headers - info.blocks; - if info.initial_block_download { - info!("waiting for IBD to finish: {} blocks left", left_blocks); - return PollResult::Retry; - } - if left_blocks > 0 { - info!("waiting for {} blocks to download", left_blocks); + if info.initial_block_download || left_blocks > 0 { + info!( + "waiting for {} blocks to download{}", + left_blocks, + if info.initial_block_download { + " (IBD)" + } else { + "" + } + ); return PollResult::Retry; } PollResult::Done(Ok(())) From 23c775fb4702ad90428522478f2af1e7aed64111 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 4 Jun 2021 12:49:37 +0300 Subject: [PATCH 032/113] Don't fail on mempool sync errors --- src/mempool.rs | 11 ++++++++--- src/tracker.rs | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/mempool.rs b/src/mempool.rs index 49e931b..acc916f 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -75,8 +75,14 @@ impl Mempool { .collect() } - pub fn sync(&mut self, daemon: &Daemon) -> Result<()> { - let txids = daemon.get_mempool_txids()?; + pub fn sync(&mut self, daemon: &Daemon) { + let txids = match daemon.get_mempool_txids() { + Ok(txids) => txids, + Err(e) => { + warn!("mempool sync failed: {}", e); + return; + } + }; debug!("loading {} mempool transactions", txids.len()); let new_txids = HashSet::::from_iter(txids); @@ -112,7 +118,6 @@ impl Mempool { added, removed, ); - Ok(()) } fn add_entry(&mut self, txid: Txid, tx: Transaction, entry: json::GetMempoolEntryResult) { diff --git a/src/tracker.rs b/src/tracker.rs index 4d7ab5d..2c00db1 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -67,7 +67,7 @@ impl Tracker { pub fn sync(&mut self, daemon: &Daemon) -> Result<()> { self.index.sync(daemon, self.index_batch_size)?; if !self.ignore_mempool { - self.mempool.sync(daemon)?; + self.mempool.sync(daemon); } // TODO: double check tip - and retry on diff Ok(()) From d2fff752cdbd93ccf0a22281ed823afe670f7be5 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 6 Jun 2021 16:15:47 +0300 Subject: [PATCH 033/113] Build hyper when 'metrics' feature is enabled --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ad678fb..aadae28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ build = "build.rs" [features] default = ["metrics"] -metrics = ["prometheus"] +metrics = ["prometheus", "hyper"] [package.metadata.configure_me] spec = "internal/config_specification.toml" @@ -27,7 +27,7 @@ configure_me = "0.4" crossbeam-channel = "0.5" dirs-next = "2.0" env_logger = "0.7" -hyper = "0.10" +hyper = { version = "0.10", optional = true } log = "0.4" prometheus = { version = "0.12", features = ["process"], optional = true } rayon = "1.5" From 188aba50f12c26c3c0722f37fd81d3a3e7bf118c Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 8 Jun 2021 20:17:12 +0300 Subject: [PATCH 034/113] Update bitcoin crate to 0.26.2 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0410539..da3b695 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.26.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec5f88a446d66e7474a3b8fa2e348320b574463fb78d799d90ba68f79f48e0e" +checksum = "6742ec672d3f12506f4ac5c0d853926ff1f94e675f60ffd3224039972bf663f1" dependencies = [ "bech32", "bitcoin_hashes", From 45833c3b61654224f30bcd3f7ab9d65f112c2f63 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 14 Jun 2021 13:19:37 +0300 Subject: [PATCH 035/113] Use parking_lot::{Mutex, RwLock} instead of std::sync --- Cargo.lock | 1 + Cargo.toml | 1 + src/cache.rs | 10 +++++----- src/daemon.rs | 6 +++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da3b695..d973d32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ dependencies = [ "env_logger", "hyper", "log 0.4.14", + "parking_lot", "prometheus", "rayon", "rocksdb", diff --git a/Cargo.toml b/Cargo.toml index aadae28..4a85932 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ dirs-next = "2.0" env_logger = "0.7" hyper = { version = "0.10", optional = true } log = "0.4" +parking_lot = "0.11" prometheus = { version = "0.12", features = ["process"], optional = true } rayon = "1.5" serde = "1.0" diff --git a/src/cache.rs b/src/cache.rs index ca4ae89..6cbff67 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,7 +1,8 @@ use bitcoin::{BlockHash, Transaction, Txid}; +use parking_lot::RwLock; use std::collections::HashMap; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; use crate::merkle::Proof; @@ -13,14 +14,14 @@ pub struct Cache { impl Cache { pub(crate) fn add_tx(&self, txid: Txid, f: impl FnOnce() -> Transaction) { - self.txs.write().unwrap().entry(txid).or_insert_with(f); + self.txs.write().entry(txid).or_insert_with(f); } pub(crate) fn get_tx(&self, txid: &Txid, f: F) -> Option where F: FnOnce(&Transaction) -> T, { - self.txs.read().unwrap().get(txid).map(f) + self.txs.read().get(txid).map(f) } pub(crate) fn add_proof(&self, blockhash: BlockHash, txid: Txid, f: F) @@ -29,7 +30,6 @@ impl Cache { { self.proofs .write() - .unwrap() .entry((blockhash, txid)) .or_insert_with(f); } @@ -38,6 +38,6 @@ impl Cache { where F: FnOnce(&Proof) -> T, { - self.proofs.read().unwrap().get(&(blockhash, txid)).map(f) + self.proofs.read().get(&(blockhash, txid)).map(f) } } diff --git a/src/daemon.rs b/src/daemon.rs index ac32f5a..86619d3 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -3,7 +3,6 @@ use anyhow::{Context, Result}; use std::io::Write; use std::iter::FromIterator; use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; -use std::sync::Mutex; use std::time::{SystemTime, UNIX_EPOCH}; use bitcoin::consensus::encode; @@ -18,6 +17,7 @@ use bitcoin::secp256k1; use bitcoin::secp256k1::rand::Rng; use bitcoin::{Amount, Block, BlockHash, Network, Transaction, Txid}; use bitcoincore_rpc::{self, json, RpcApi}; +use parking_lot::Mutex; use crate::{ chain::{Chain, NewHeader}, @@ -239,7 +239,7 @@ impl Daemon { } pub(crate) fn get_new_headers(&self, chain: &Chain) -> Result> { - let mut conn = self.p2p.lock().unwrap(); + let mut conn = self.p2p.lock(); let msg = GetHeadersMessage::new(chain.locator(), BlockHash::default()); conn.send(NetworkMessage::GetHeaders(msg))?; @@ -278,7 +278,7 @@ impl Daemon { .map(|h| Inventory::WitnessBlock(*h)) .collect(); debug!("loading {} blocks", blockhashes.len()); - let mut conn = self.p2p.lock().unwrap(); + let mut conn = self.p2p.lock(); conn.send(NetworkMessage::GetData(inv))?; for hash in blockhashes { match conn From af834a009f057fb31541b812e278b094514b4e8b Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 18 Jun 2021 12:07:22 +0300 Subject: [PATCH 036/113] Remove unused code --- src/map.rs | 77 ------------------------------------------------------ 1 file changed, 77 deletions(-) delete mode 100644 src/map.rs diff --git a/src/map.rs b/src/map.rs deleted file mode 100644 index 4d72a22..0000000 --- a/src/map.rs +++ /dev/null @@ -1,77 +0,0 @@ -use bitcoin::{BlockHash, BlockHeader}; - -use std::collections::HashMap; - -#[derive(Default)] -struct Chain { - by_height: Vec, -} - -impl Chain { - fn build(tip: BlockHash, by_hash: &HashMap) -> Self { - // verify full chain till genesis - let mut by_height = vec![]; - let mut blockhash = tip; - while blockhash != BlockHash::default() { - by_height.push(blockhash); - blockhash = match by_hash.get(&blockhash) { - Some(header) => header.prev_blockhash, - None => panic!("missing block header: {}", blockhash), - }; - } - by_height.reverse(); - Self { by_height } - } - - fn len(&self) -> usize { - self.by_height.len() - } - - fn tip(&self) -> Option<&BlockHash> { - self.by_height.last() - } -} - -#[derive(Default)] -pub struct BlockMap { - by_hash: HashMap, - chain: Chain, -} - -impl BlockMap { - pub(crate) fn new(headers: Vec) -> Self { - let mut map = Self::default(); - map.add_headers(headers); - map - } - - pub fn chain(&self) -> &[BlockHash] { - &self.chain.by_height - } - - /// May return stale headers - pub fn get_header(&self, hash: &BlockHash) -> Option<&BlockHeader> { - self.by_hash.get(hash) - } - - fn add_headers(&mut self, headers: Vec) { - let total_blocks = headers.len(); - let mut new_blocks = 0usize; - for header in headers { - let hash = header.block_hash(); - self.by_hash.entry(hash).or_insert_with(|| { - new_blocks += 1; - header - }); - } - debug!("added {}/{} headers", new_blocks, total_blocks,); - } - - pub fn update(&mut self, tip: BlockHash, headers: Vec) { - self.add_headers(headers); - let chain = Chain::build(tip, &self.by_hash); - assert_eq!(chain.tip(), Some(&tip)); - info!("verified {} headers, tip={}", chain.len(), tip); - self.chain = chain; - } -} From b9e8aa8e7c00d0727b1d46a5fb7e02e041c50962 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 18 Jun 2021 13:00:37 +0300 Subject: [PATCH 037/113] Add a few unittests for Chain --- src/chain.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/chain.rs b/src/chain.rs index f77238d..a3cbfa9 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -128,3 +128,101 @@ impl Chain { result } } + +#[cfg(test)] +mod tests { + use super::{Chain, NewHeader}; + use bitcoin::consensus::deserialize; + use bitcoin::hashes::hex::{FromHex, ToHex}; + use bitcoin::network::constants::Network::Regtest; + use bitcoin::BlockHeader; + + #[test] + fn test_genesis() { + let regtest = Chain::new(Regtest); + assert_eq!(regtest.height(), 0); + assert_eq!( + regtest.tip().to_hex(), + "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" + ); + } + + #[test] + fn test_updates() { + let hex_headers = vec![ +"0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1d14d3c7ff12d6adf494ebbcfba69baa915a066358b68a2b8c37126f74de396b1d61cc60ffff7f2000000000", +"00000020d700ae5d3c705702e0a5d9ababd22ded079f8a63b880b1866321d6bfcb028c3fc816efcf0e84ccafa1dda26be337f58d41b438170c357cda33a68af5550590bc1e61cc60ffff7f2004000000", +"00000020d13731bc59bc0989e06a5e7cab9843a4e17ad65c7ca47cd77f50dfd24f1f55793f7f342526aca9adb6ce8f33d8a07662c97d29d83b9e18117fb3eceecb2ab99b1e61cc60ffff7f2001000000", +"00000020a603def3e1255cadfb6df072946327c58b344f9bfb133e8e3e280d1c2d55b31c731a68f70219472864a7cb010cd53dc7e0f67e57f7d08b97e5e092b0c3942ad51f61cc60ffff7f2001000000", +"0000002041dd202b3b2edcdd3c8582117376347d48ff79ff97c95e5ac814820462012e785142dc360975b982ca43eecd14b4ba6f019041819d4fc5936255d7a2c45a96651f61cc60ffff7f2000000000", +"0000002072e297a2d6b633c44f3c9b1a340d06f3ce4e6bcd79ebd4c4ff1c249a77e1e37c59c7be1ca0964452e1735c0d2740f0d98a11445a6140c36b55770b5c0bcf801f1f61cc60ffff7f2000000000", +"000000200c9eb5889a8e924d1c4e8e79a716514579e41114ef37d72295df8869d6718e4ac5840f28de43ff25c7b9200aaf7873b20587c92827eaa61943484ca828bdd2e11f61cc60ffff7f2000000000", +"000000205873f322b333933e656b07881bb399dae61a6c0fa74188b5fb0e3dd71c9e2442f9e2f433f54466900407cf6a9f676913dd54aad977f7b05afcd6dcd81e98ee752061cc60ffff7f2004000000", +"00000020fd1120713506267f1dba2e1856ca1d4490077d261cde8d3e182677880df0d856bf94cfa5e189c85462813751ab4059643759ed319a81e0617113758f8adf67bc2061cc60ffff7f2000000000", +"000000200030d7f9c11ef35b89a0eefb9a5e449909339b5e7854d99804ea8d6a49bf900a0304d2e55fe0b6415949cff9bca0f88c0717884a5e5797509f89f856af93624a2061cc60ffff7f2002000000", + ]; + let headers: Vec = hex_headers + .iter() + .map(|hex_header| deserialize(&Vec::from_hex(hex_header).unwrap()).unwrap()) + .collect(); + + for chunk_size in 1..hex_headers.len() { + let mut regtest = Chain::new(Regtest); + let mut height = 0; + let mut tip = regtest.tip(); + for chunk in headers.chunks(chunk_size) { + let mut update = vec![]; + for header in chunk { + height += 1; + tip = header.block_hash(); + update.push(NewHeader::from((*header, height))) + } + regtest.update(update); + assert_eq!(regtest.tip(), tip); + assert_eq!(regtest.height(), height); + } + assert_eq!(regtest.tip(), headers.last().unwrap().block_hash()); + assert_eq!(regtest.height(), headers.len()); + } + + // test loading from a list of headers and tip + let mut regtest = Chain::new(Regtest); + regtest.load(headers.clone(), headers.last().unwrap().block_hash()); + assert_eq!(regtest.height(), headers.len()); + + // test getters + for (header, height) in headers.iter().zip(1usize..) { + assert_eq!(regtest.get_block_header(height), Some(header)); + assert_eq!(regtest.get_block_hash(height), Some(header.block_hash())); + assert_eq!(regtest.get_block_height(&header.block_hash()), Some(height)); + } + + // test chain shortening + for i in (0..=headers.len()).rev() { + let header = *regtest.get_block_header(i).unwrap(); + let hash = regtest.get_block_hash(i).unwrap(); + assert_eq!(regtest.get_block_height(&hash), Some(i)); + regtest.update(vec![NewHeader::from((header, i))]); + assert_eq!(regtest.height(), i); + assert_eq!(regtest.tip(), hash); + } + assert_eq!(regtest.height(), 0); + assert_eq!( + regtest.tip().to_hex(), + "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" + ); + + // test reorg + let mut regtest = Chain::new(Regtest); + regtest.load(headers.clone(), headers.last().unwrap().block_hash()); + let height = regtest.height(); + + let new_header: BlockHeader = deserialize(&Vec::from_hex("000000200030d7f9c11ef35b89a0eefb9a5e449909339b5e7854d99804ea8d6a49bf900a0304d2e55fe0b6415949cff9bca0f88c0717884a5e5797509f89f856af93624a7a6bcc60ffff7f2000000000").unwrap()).unwrap(); + regtest.update(vec![NewHeader::from((new_header, height))]); + assert_eq!(regtest.height(), height); + assert_eq!( + regtest.tip().to_hex(), + "0e16637fe0700a7c52e9a6eaa58bd6ac7202652103be8f778680c66f51ad2e9b" + ); + } +} From 423e4c7922aec4c484678fb15e44b06b56517113 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 13 Jun 2021 14:57:55 +0300 Subject: [PATCH 038/113] Rewrite Python scripts Also, improve balance/history display --- contrib/addr.py | 35 ----------- contrib/client.py | 22 ++++--- contrib/get_tip.py | 2 +- contrib/get_tx.py | 2 +- contrib/health_check.py | 2 +- contrib/history.py | 132 ++++++++++++++++++++++++++++++++++++++++ contrib/history.sh | 4 ++ contrib/tx_fee.py | 7 +-- scripts/run.sh | 19 ------ 9 files changed, 154 insertions(+), 71 deletions(-) delete mode 100755 contrib/addr.py create mode 100755 contrib/history.py create mode 100755 contrib/history.sh delete mode 100755 scripts/run.sh diff --git a/contrib/addr.py b/contrib/addr.py deleted file mode 100755 index 5e8bae7..0000000 --- a/contrib/addr.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -import hashlib -import sys -import argparse - -import client - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--testnet', action='store_true') - parser.add_argument('address', nargs='+') - args = parser.parse_args() - - if args.testnet: - port = 60001 - from pycoin.symbols.xtn import network - else: - port = 50001 - from pycoin.symbols.btc import network - - conn = client.Client(('localhost', port)) - for addr in args.address: - script = network.parse.address(addr).script() - script_hash = hashlib.sha256(script).digest()[::-1].hex() - reply = conn.call('blockchain.scripthash.subscribe', script_hash) - print(f'{reply}') - reply = conn.call('blockchain.scripthash.get_history', script_hash) - result = reply['result'] - print(f'{addr} has {len(result)} transactions:') - for tx in result: - print(f'* {tx["tx_hash"]}') - - -if __name__ == '__main__': - main() diff --git a/contrib/client.py b/contrib/client.py index 26bbf24..74e74fe 100644 --- a/contrib/client.py +++ b/contrib/client.py @@ -7,16 +7,18 @@ class Client: self.f = self.s.makefile('r') self.id = 0 - def request(self, method, *args): - self.id += 1 - return { - 'id': self.id, - 'method': method, - 'params': list(args), - 'jsonrpc': '2.0', - } + def call(self, requests): + requests = list(requests) + for request in requests: + request['id'] = self.id + request['jsonrpc'] = '2.0' + self.id += 1 - def call(self, *requests): msg = json.dumps(requests) + '\n' self.s.sendall(msg.encode('ascii')) - return json.loads(self.f.readline()) + response = json.loads(self.f.readline()) + return [r['result'] for r in response] + + +def request(method, *args): + return {'method': method, 'params': list(args)} diff --git a/contrib/get_tip.py b/contrib/get_tip.py index 7ff8ac7..a851a0d 100755 --- a/contrib/get_tip.py +++ b/contrib/get_tip.py @@ -10,7 +10,7 @@ def main(): args = parser.parse_args() conn = client.Client((args.host, args.port)) - print(conn.call(conn.request("blockchain.headers.subscribe"))[0]["result"]) + print(conn.call([client.request("blockchain.headers.subscribe")])) if __name__ == '__main__': main() diff --git a/contrib/get_tx.py b/contrib/get_tx.py index af8760b..3b4f99a 100755 --- a/contrib/get_tx.py +++ b/contrib/get_tx.py @@ -9,7 +9,7 @@ def main(): args = parser.parse_args() conn = client.Client(("localhost", 50001)) - tx = conn.call("blockchain.transaction.get", args.txid, True)["result"] + tx, = conn.call([client.request("blockchain.transaction.get", args.txid, True)]) print(json.dumps(tx)) if __name__ == "__main__": diff --git a/contrib/health_check.py b/contrib/health_check.py index bb7f1be..9f2879e 100755 --- a/contrib/health_check.py +++ b/contrib/health_check.py @@ -10,7 +10,7 @@ def main(): args = parser.parse_args() conn = client.Client((args.host, args.port)) - print(json.dumps(conn.call("server.version", "health_check", "1.4")["result"])) + print(json.dumps(conn.call([client.request("server.version", "health_check", "1.4")]))) if __name__ == '__main__': main() diff --git a/contrib/history.py b/contrib/history.py new file mode 100755 index 0000000..3b5dd90 --- /dev/null +++ b/contrib/history.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +import argparse +import datetime +import hashlib +import io +import sys + +import pycoin +from logbook import Logger, StreamHandler +import prettytable + +import client + +log = Logger('electrum') + + +def _script_hash(script): + return hashlib.sha256(script).digest()[::-1].hex() + + +def show_rows(rows, field_names): + t = prettytable.PrettyTable() + t.field_names = field_names + t.add_rows(rows) + for f in t.field_names: + if "mBTC" in f: + t.align[f] = "r" + print(t) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--testnet', action='store_true') + parser.add_argument('address', nargs='+') + args = parser.parse_args() + + if args.testnet: + port = 60001 + from pycoin.symbols.xtn import network + else: + port = 50001 + from pycoin.symbols.btc import network + + hostport = ('localhost', port) + log.info('connecting to {}:{}', *hostport) + conn = client.Client(hostport) + + tip, = conn.call([client.request('blockchain.headers.subscribe')]) + + script_hashes = set( + _script_hash(network.parse.address(addr).script()) + for addr in args.address + ) + + conn.call( + client.request('blockchain.scripthash.subscribe', script_hash) + for script_hash in script_hashes + ) + log.info('subscribed to {} scripthashes', len(script_hashes)) + + histories = conn.call( + client.request('blockchain.scripthash.get_history', script_hash) + for script_hash in script_hashes + ) + txids_map = dict( + (tx['tx_hash'], tx['height'] if tx['height'] > 0 else None) + for history in histories + for tx in history + ) + log.info('got history of {} transactions', len(txids_map)) + + txs = map(network.tx.from_hex, conn.call( + client.request('blockchain.transaction.get', txid) + for txid in txids_map.keys() + )) + txs_map = dict(zip(txids_map.keys(), txs)) + log.info('loaded {} transactions', len(txids_map)) + + confirmed_txids = {txid: height for txid, height in txids_map.items() if height is not None} + + heights = set(confirmed_txids.values()) + def _parse_header(header): + return network.block.parse_as_header(io.BytesIO(bytes.fromhex(header))) + headers = map(_parse_header, conn.call( + client.request('blockchain.block.header', height) + for height in heights + )) + def _parse_timestamp(header): + return datetime.datetime.utcfromtimestamp(header.timestamp).strftime('%Y-%m-%dT%H:%M:%SZ') + timestamps = map(_parse_timestamp, headers) + timestamps_map = dict(zip(heights, timestamps)) + log.info('loaded {} header timestamps', len(heights)) + + proofs = conn.call( + client.request('blockchain.transaction.get_merkle', txid, height) + for txid, height in confirmed_txids.items() + ) + log.info('loaded {} merkle proofs', len(proofs)) # TODO: verify proofs + + sorted_txdata = sorted( + (proof['block_height'], proof['pos'], txid) + for proof, txid in zip(proofs, confirmed_txids) + ) + + utxos = {} + balance = 0 + + rows = [] + for block_height, block_pos, txid in sorted_txdata: + tx_obj = txs_map[txid] + for txi in tx_obj.txs_in: + utxos.pop((str(txi.previous_hash), txi.previous_index), None) + + for index, txo in enumerate(tx_obj.txs_out): + if _script_hash(txo.puzzle_script()) in script_hashes: + utxos[(txid, index)] = txo + + diff = sum(txo.coin_value for txo in utxos.values()) - balance + balance += diff + confirmations = tip['height'] - block_height + 1 + rows.append([txid, timestamps_map[block_height], block_height, confirmations, f'{diff/1e5:,.5f}', f'{balance/1e5:,.5f}']) + show_rows(rows, ["txid", "block timestamp", "height", "confirmations", "delta (mBTC)", "total (mBTC)"]) + + tip_header = _parse_header(tip['hex']) + log.info('tip={}, height={} @ {}', tip_header.id(), tip['height'], _parse_timestamp(tip_header)) + + unconfirmed = {txs_map[txid] for txid, height in txids_map.items() if height is None} + # TODO: show unconfirmed balance + +if __name__ == '__main__': + StreamHandler(sys.stderr).push_application() + main() diff --git a/contrib/history.sh b/contrib/history.sh new file mode 100755 index 0000000..711a776 --- /dev/null +++ b/contrib/history.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -eu +cd `dirname $0` +.env/bin/python history.py $* diff --git a/contrib/tx_fee.py b/contrib/tx_fee.py index c8d69f2..a53bc94 100755 --- a/contrib/tx_fee.py +++ b/contrib/tx_fee.py @@ -8,15 +8,14 @@ def main(): args = parser.parse_args() conn = client.Client(("localhost", 50001)) - tx = conn.call(conn.request("blockchain.transaction.get", args.txid, True))[0]["result"] + tx, = conn.call([client.request("blockchain.transaction.get", args.txid, True)]) requests = [] for vin in tx["vin"]: prev_txid = vin["txid"] - requests.append(conn.request("blockchain.transaction.get", prev_txid, True)) + requests.append(client.request("blockchain.transaction.get", prev_txid, True)) fee = 0 - for vin, response in zip(tx["vin"], conn.call(*requests)): - prev_tx = response["result"] + for vin, prev_tx in zip(tx["vin"], conn.call(requests)): txo = prev_tx["vout"][vin["vout"]] fee += txo["value"] diff --git a/scripts/run.sh b/scripts/run.sh deleted file mode 100755 index 864764e..0000000 --- a/scripts/run.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -set -eu -trap 'kill $(jobs -p)' EXIT - -DELAY=5 -LOG=/tmp/electrs.log -CARGO="cargo +stable" - -tail -v -n0 -F "$LOG" & - -export RUST_BACKTRACE=1 -while : -do - $CARGO fmt - $CARGO check --release - $CARGO run --release -- $* 2>> "$LOG" - echo "Restarting in $DELAY seconds..." - sleep $DELAY -done From f5884cd172eaf529634072d08df2cf2f801846cc Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 18 Jun 2021 20:37:53 +0300 Subject: [PATCH 039/113] Simplify indexing code a bit --- src/index.rs | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/index.rs b/src/index.rs index 8f1c3a0..730c66d 100644 --- a/src/index.rs +++ b/src/index.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use crate::{ chain::Chain, daemon::Daemon, - db, + db::{DBStore, Row, WriteBatch}, metrics::{Histogram, Metrics}, types::{HeaderRow, ScriptHash, ScriptHashRow, SpendingPrefixRow, TxidRow}, }; @@ -40,15 +40,15 @@ impl Stats { } } - fn report_stats(&self, batch: &db::WriteBatch) { - self.update_size - .observe("write_funding_rows", db_rows_size(&batch.funding_rows)); - self.update_size - .observe("write_spending_rows", db_rows_size(&batch.spending_rows)); - self.update_size - .observe("write_txid_rows", db_rows_size(&batch.txid_rows)); - self.update_size - .observe("write_header_rows", db_rows_size(&batch.header_rows)); + fn observe_size(&self, label: &str, rows: &[Row]) { + self.update_size.observe(label, db_rows_size(rows)); + } + + fn report_stats(&self, batch: &WriteBatch) { + self.observe_size("write_funding_rows", &batch.funding_rows); + self.observe_size("write_spending_rows", &batch.spending_rows); + self.observe_size("write_txid_rows", &batch.txid_rows); + self.observe_size("write_header_rows", &batch.header_rows); debug!( "writing {} funding and {} spending rows from {} transactions, {} blocks", batch.funding_rows.len(), @@ -67,14 +67,16 @@ struct IndexResult { } impl IndexResult { - fn extend(&self, batch: &mut db::WriteBatch) { + fn extend(&self, batch: &mut WriteBatch) { let funding_rows = self.funding_rows.iter().map(ScriptHashRow::to_db_row); - let spending_rows = self.spending_rows.iter().map(SpendingPrefixRow::to_db_row); - let txid_rows = self.txid_rows.iter().map(TxidRow::to_db_row); - batch.funding_rows.extend(funding_rows); + + let spending_rows = self.spending_rows.iter().map(SpendingPrefixRow::to_db_row); batch.spending_rows.extend(spending_rows); + + let txid_rows = self.txid_rows.iter().map(TxidRow::to_db_row); batch.txid_rows.extend(txid_rows); + batch.header_rows.push(self.header_row.to_db_row()); batch.tip_row = serialize(&self.header_row.header.block_hash()).into_boxed_slice(); } @@ -82,13 +84,13 @@ impl IndexResult { /// Confirmed transactions' address index pub struct Index { - store: db::DBStore, + store: DBStore, chain: Chain, stats: Stats, } impl Index { - pub(crate) fn load(store: db::DBStore, mut chain: Chain, metrics: &Metrics) -> Result { + pub(crate) fn load(store: DBStore, mut chain: Chain, metrics: &Metrics) -> Result { if let Some(row) = store.get_tip() { let tip = deserialize(&row).expect("invalid tip"); let headers = store @@ -154,7 +156,7 @@ impl Index { let mut heights_map: HashMap = chunk.iter().map(|h| (h.hash(), h.height())).collect(); - let mut batch = db::WriteBatch::default(); + let mut batch = WriteBatch::default(); daemon.for_blocks(blockhashes, |blockhash, block| { let height = heights_map.remove(&blockhash).expect("unexpected block"); @@ -173,7 +175,7 @@ impl Index { } } -fn db_rows_size(rows: &[db::Row]) -> usize { +fn db_rows_size(rows: &[Row]) -> usize { rows.iter().map(|key| key.len()).sum() } From cbf153fb095792007636d02936a168a1d6fc3a27 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 25 Jun 2021 18:12:27 +0300 Subject: [PATCH 040/113] Update dependencies --- Cargo.lock | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d973d32..1f19932 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,9 +17,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" +checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" [[package]] name = "atty" @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e6d72ba9671cb0929b5620e1bc30cdf5e206ccb3dbaa8964d67f6efc1e16129" +checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1" dependencies = [ "serde", ] @@ -393,9 +393,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] @@ -513,9 +513,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" +checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" [[package]] name = "libloading" @@ -719,9 +719,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.23.0" +version = "2.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45604fc7a88158e7d514d8e22e14ac746081e7a70d7690074dd0029ee37458d6" +checksum = "db50e77ae196458ccd3dc58a31ea1a90b0698ab1b7928d89f644c25d72070267" [[package]] name = "quick-error" @@ -880,9 +880,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" dependencies = [ "bitflags", ] @@ -955,9 +955,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "secp256k1" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee5070fdc6f26ca5be6dcfc3d07c76fdb974a63a8b246b459854274145f5a258" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" dependencies = [ "rand", "secp256k1-sys", @@ -966,9 +966,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e4b6455ee49f5901c8985b88f98fb0a0e1d90a6661f5a03f4888bd987dad29" +checksum = "827cb7cce42533829c792fc51b82fbf18b125b45a702ef2c8be77fce65463a7b" dependencies = [ "cc", ] @@ -1012,9 +1012,9 @@ checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "signal-hook" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" +checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" dependencies = [ "libc", "signal-hook-registry", @@ -1022,9 +1022,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] @@ -1037,9 +1037,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "syn" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" dependencies = [ "proc-macro2", "quote", @@ -1141,9 +1141,9 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33717dca7ac877f497014e10d73f3acf948c342bee31b5ca7892faf94ccc6b49" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] From 1a6d03b04a4bf2e7e67efd7c2ec6456a68bfe514 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 29 Jun 2021 14:38:12 +0300 Subject: [PATCH 041/113] Ignore IntelliJ .idea/ dir --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 62a7a33..08fd88e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ electrs.toml data/ tests/bitcoin-* tests/bin +.idea/ From 05d6dc1975a2883e26fc9f6886f19711924e7b94 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 29 Jun 2021 14:40:26 +0300 Subject: [PATCH 042/113] Elide lifetime at iter_prefix_cf() --- src/db.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db.rs b/src/db.rs index 4f0e80a..e50f954 100644 --- a/src/db.rs +++ b/src/db.rs @@ -140,7 +140,7 @@ impl DBStore { self.iter_prefix_cf(self.txid_cf(), prefix) } - fn iter_prefix_cf<'a>(&'a self, cf: &rocksdb::ColumnFamily, prefix: Row) -> ScanIterator<'a> { + fn iter_prefix_cf(&self, cf: &rocksdb::ColumnFamily, prefix: Row) -> ScanIterator { let mode = rocksdb::IteratorMode::From(&prefix, rocksdb::Direction::Forward); let iter = self.db.iterator_cf(cf, mode); ScanIterator { From d4134113cbff3a8b962df36823b236e5610efae9 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 29 Jun 2021 14:58:02 +0300 Subject: [PATCH 043/113] Rename PREFIX_LEN -> HASH_PREFIX_LEN --- src/types.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/types.rs b/src/types.rs index 26c6cc4..480f014 100644 --- a/src/types.rs +++ b/src/types.rs @@ -51,17 +51,17 @@ impl ScriptHash { } fn prefix(&self) -> ScriptHashPrefix { - let mut prefix = [0u8; PREFIX_LEN]; - prefix.copy_from_slice(&self.0[..PREFIX_LEN]); + let mut prefix = [0u8; HASH_PREFIX_LEN]; + prefix.copy_from_slice(&self.0[..HASH_PREFIX_LEN]); ScriptHashPrefix { prefix } } } -const PREFIX_LEN: usize = 8; +const HASH_PREFIX_LEN: usize = 8; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct ScriptHashPrefix { - prefix: [u8; PREFIX_LEN], + prefix: [u8; HASH_PREFIX_LEN], } impl_consensus_encoding!(ScriptHashPrefix, prefix); @@ -78,7 +78,7 @@ impl_consensus_encoding!(ScriptHashRow, prefix, height); impl ScriptHashRow { pub(crate) fn scan_prefix(scripthash: ScriptHash) -> Box<[u8]> { - scripthash.0[..PREFIX_LEN].to_vec().into_boxed_slice() + scripthash.0[..HASH_PREFIX_LEN].to_vec().into_boxed_slice() } pub(crate) fn new(scripthash: ScriptHash, height: usize) -> Self { @@ -115,13 +115,13 @@ hash_newtype!( #[derive(Debug, Serialize, Deserialize, PartialEq)] struct SpendingPrefix { - prefix: [u8; PREFIX_LEN], + prefix: [u8; HASH_PREFIX_LEN], } impl_consensus_encoding!(SpendingPrefix, prefix); fn spending_prefix(prev: OutPoint) -> SpendingPrefix { - let txid_prefix = &prev.txid[..PREFIX_LEN]; + let txid_prefix = &prev.txid[..HASH_PREFIX_LEN]; let value = u64::from_be_bytes(txid_prefix.try_into().unwrap()); let value = value.wrapping_add(prev.vout.into()); SpendingPrefix { @@ -166,14 +166,14 @@ impl SpendingPrefixRow { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct TxidPrefix { - prefix: [u8; PREFIX_LEN], + prefix: [u8; HASH_PREFIX_LEN], } impl_consensus_encoding!(TxidPrefix, prefix); fn txid_prefix(txid: &Txid) -> TxidPrefix { - let mut prefix = [0u8; PREFIX_LEN]; - prefix.copy_from_slice(&txid[..PREFIX_LEN]); + let mut prefix = [0u8; HASH_PREFIX_LEN]; + prefix.copy_from_slice(&txid[..HASH_PREFIX_LEN]); TxidPrefix { prefix } } From 94f0afaf853cf1e0287eb1f660a0c5c7d1770aeb Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 29 Jun 2021 14:59:08 +0300 Subject: [PATCH 044/113] Remove eprintln in tests --- src/merkle.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/merkle.rs b/src/merkle.rs index d50f6a5..6c48f02 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -106,7 +106,6 @@ mod tests { .join("tests") .join("blocks") .join(block_hash_hex); - eprintln!("{:?}", path); let data = std::fs::read(path).unwrap(); let block: Block = deserialize(&data).unwrap(); block.txdata.iter().map(|tx| tx.txid()).collect() From 0d44ec67e254aea59a05867acc29e07f9327e0d1 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 29 Jun 2021 15:00:06 +0300 Subject: [PATCH 045/113] Fix a typo --- src/chain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chain.rs b/src/chain.rs index a3cbfa9..746c82c 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -29,7 +29,7 @@ impl NewHeader { } } -/// Curent blockchain headers' list +/// Current blockchain headers' list pub struct Chain { headers: Vec<(BlockHash, BlockHeader)>, heights: HashMap, From f03c1d093839700c4384fb5195a8c309cefc23c4 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 20 Jun 2021 21:57:01 +0300 Subject: [PATCH 046/113] Use JSONRPC error codes See https://www.jsonrpc.org/specification#error_object --- src/daemon.rs | 18 +++- src/electrum.rs | 259 ++++++++++++++++++++++++++++++++++-------------- src/server.rs | 38 ++++--- 3 files changed, 217 insertions(+), 98 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 86619d3..61540ff 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -91,10 +91,6 @@ enum PollResult { } fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { - use bitcoincore_rpc::{ - jsonrpc::error::Error::Rpc as ServerError, Error::JsonRpc as JsonRpcError, - }; - match client.get_blockchain_info() { Ok(info) => { let left_blocks = info.headers - info.blocks; @@ -113,7 +109,7 @@ fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { PollResult::Done(Ok(())) } Err(err) => { - if let JsonRpcError(ServerError(ref e)) = err { + if let Some(e) = extract_bitcoind_error(&err) { if e.code == -28 { info!("waiting for RPC warmup: {}", e.message); return PollResult::Retry; @@ -317,3 +313,15 @@ fn build_version_message() -> NetworkMessage { relay: false, }) } + +pub(crate) type RpcError = bitcoincore_rpc::jsonrpc::error::RpcError; + +pub(crate) fn extract_bitcoind_error(err: &bitcoincore_rpc::Error) -> Option<&RpcError> { + use bitcoincore_rpc::{ + jsonrpc::error::Error::Rpc as ServerError, Error::JsonRpc as JsonRpcError, + }; + match err { + JsonRpcError(ServerError(e)) => Some(e), + _ => None, + } +} diff --git a/src/electrum.rs b/src/electrum.rs index c921bac..8a0bec9 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -5,15 +5,21 @@ use bitcoin::{ BlockHash, Txid, }; use rayon::prelude::*; -use serde_derive::{Deserialize, Serialize}; -use serde_json::{from_value, json, Value}; +use serde_derive::Deserialize; +use serde_json::{self, json, Value}; use std::collections::HashMap; use std::iter::FromIterator; use crate::{ - cache::Cache, config::Config, daemon::Daemon, merkle::Proof, metrics::Histogram, - status::Status, tracker::Tracker, types::ScriptHash, + cache::Cache, + config::Config, + daemon::{self, extract_bitcoind_error, Daemon}, + merkle::Proof, + metrics::Histogram, + status::Status, + tracker::Tracker, + types::ScriptHash, }; const ELECTRS_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -28,10 +34,9 @@ pub struct Client { status: HashMap, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Deserialize)] struct Request { id: Value, - jsonrpc: String, method: String, #[serde(default)] @@ -68,6 +73,42 @@ impl From for (Txid, bool) { } } +enum StandardError { + ParseError, + InvalidRequest, + MethodNotFound, + InvalidParams, +} + +enum RpcError { + // JSON-RPC spec errors + Standard(StandardError), + // Electrum-specific errors + BadRequest(anyhow::Error), + DaemonError(daemon::RpcError), +} + +impl RpcError { + fn to_value(&self) -> Value { + match self { + RpcError::Standard(err) => match err { + StandardError::ParseError => json!({"code": -32700, "message": "parse error"}), + StandardError::InvalidRequest => { + json!({"code": -32600, "message": "invalid request"}) + } + StandardError::MethodNotFound => { + json!({"code": -32601, "message": "method not found"}) + } + StandardError::InvalidParams => { + json!({"code": -32602, "message": "invalid params"}) + } + }, + RpcError::BadRequest(err) => json!({"code": 1, "message": err.to_string()}), + RpcError::DaemonError(err) => json!({"code": 2, "message": err.message}), + } + } +} + /// Electrum RPC handler pub struct Rpc { tracker: Tracker, @@ -96,7 +137,7 @@ impl Rpc { self.tracker.sync(&self.daemon) } - pub fn update_client(&self, client: &mut Client) -> Result> { + pub fn update_client(&self, client: &mut Client) -> Result> { let chain = self.tracker.chain(); let mut notifications = client .status @@ -129,67 +170,7 @@ impl Rpc { )); } } - Ok(notifications) - } - - pub fn handle_request(&self, client: &mut Client, value: Value) -> Result { - let requests: Requests = from_value(value).context("invalid request")?; - match requests { - Requests::Single(request) => self.handle_single_request(client, request), - Requests::Batch(requests) => requests - .into_iter() - .map(|request| self.handle_single_request(client, request)) - .collect::>>() - .map(|results| json!(results)), - } - } - - fn handle_single_request(&self, client: &mut Client, request: Request) -> Result { - let Request { - id, - jsonrpc, - method, - params, - } = request; - self.rpc_duration.observe_duration(&method, || { - let result = match method.as_str() { - "blockchain.scripthash.get_history" => { - self.scripthash_get_history(client, from_value(params)?) - } - "blockchain.scripthash.subscribe" => { - self.scripthash_subscribe(client, from_value(params)?) - } - "blockchain.transaction.broadcast" => { - self.transaction_broadcast(from_value(params)?) - } - "blockchain.transaction.get" => self.transaction_get(from_value(params)?), - "blockchain.transaction.get_merkle" => { - self.transaction_get_merkle(from_value(params)?) - } - "server.banner" => Ok(json!(self.banner)), - "server.donation_address" => Ok(Value::Null), - "server.peers.subscribe" => Ok(json!([])), - "blockchain.block.header" => self.block_header(from_value(params)?), - "blockchain.block.headers" => self.block_headers(from_value(params)?), - "blockchain.estimatefee" => self.estimate_fee(from_value(params)?), - "blockchain.headers.subscribe" => self.headers_subscribe(client), - "blockchain.relayfee" => self.relayfee(), - "mempool.get_fee_histogram" => self.get_fee_histogram(), - "server.ping" => Ok(Value::Null), - "server.version" => self.version(from_value(params)?), - &_ => bail!("unknown method '{}' with {}", method, params,), - }; - - Ok(match result { - Ok(value) => json!({"jsonrpc": jsonrpc, "id": id, "result": value}), - Err(err) => { - let msg = format!("RPC failed: {:#}", err); - warn!("{}", msg); - let error = json!({"code": 1, "message": msg}); - json!({"jsonrpc": jsonrpc, "id": id, "error": error}) - } - }) - }) + Ok(notifications.into_iter().map(|v| v.to_string()).collect()) } fn headers_subscribe(&self, client: &mut Client) -> Result { @@ -319,7 +300,10 @@ impl Rpc { fn version(&self, (client_id, client_version): (String, Version)) -> Result { match client_version { - Version::Single(v) if v == PROTOCOL_VERSION => (), + Version::Single(v) if v == PROTOCOL_VERSION => { + let server_id = format!("electrs/{}", ELECTRS_VERSION); + Ok(json!([server_id, PROTOCOL_VERSION])) + } _ => { bail!( "{} requested {:?}, server supports {}", @@ -328,12 +312,143 @@ impl Rpc { PROTOCOL_VERSION ); } - }; - let server_id = format!("electrs/{}", ELECTRS_VERSION); - Ok(json!([server_id, PROTOCOL_VERSION])) + } } + + pub fn handle_request(&self, client: &mut Client, line: &str) -> String { + let error_msg_no_id = |err| error_msg(Value::Null, RpcError::Standard(err)); + let response: Value = match serde_json::from_str(line) { + // parse JSON from str + Ok(value) => match serde_json::from_value(value) { + // parse RPC from JSON + Ok(requests) => match requests { + Requests::Single(request) => self.call(client, request), + Requests::Batch(requests) => json!(requests + .into_iter() + .map(|request| self.call(client, request)) + .collect::>()), + }, + Err(err) => { + warn!("invalid RPC request ({:?}): {}", line, err); + error_msg_no_id(StandardError::InvalidRequest) + } + }, + Err(err) => { + warn!("invalid JSON ({:?}): {}", line, err); + error_msg_no_id(StandardError::ParseError) + } + }; + response.to_string() + } + + fn call(&self, client: &mut Client, request: Request) -> Value { + let Request { id, method, params } = request; + let call = match Call::parse(&method, params) { + Ok(call) => call, + Err(err) => return error_msg(id, RpcError::Standard(err)), + }; + self.rpc_duration.observe_duration(&method, || { + let result = match call { + Call::Banner => Ok(json!(self.banner)), + Call::BlockHeader(args) => self.block_header(args), + Call::BlockHeaders(args) => self.block_headers(args), + Call::Donation => Ok(Value::Null), + Call::EstimateFee(args) => self.estimate_fee(args), + Call::HeadersSubscribe => self.headers_subscribe(client), + Call::MempoolFeeHistogram => self.get_fee_histogram(), + Call::PeersSubscribe => Ok(json!([])), + Call::Ping => Ok(Value::Null), + Call::RelayFee => self.relayfee(), + Call::ScriptHashGetHistory(args) => self.scripthash_get_history(client, args), + Call::ScriptHashSubscribe(args) => self.scripthash_subscribe(client, args), + Call::TransactionBroadcast(args) => self.transaction_broadcast(args), + Call::TransactionGet(args) => self.transaction_get(args), + Call::TransactionGetMerkle(args) => self.transaction_get_merkle(args), + Call::Version(args) => self.version(args), + }; + match result { + Ok(value) => result_msg(id, value), + Err(err) => { + warn!("RPC {} failed: {:#}", method, err); + match err + .downcast_ref::() + .and_then(extract_bitcoind_error) + { + Some(e) => error_msg(id, RpcError::DaemonError(e.clone())), + None => error_msg(id, RpcError::BadRequest(err)), + } + } + } + }) + } +} + +#[derive(Deserialize)] +enum Call { + Banner, + BlockHeader((usize,)), + BlockHeaders((usize, usize)), + TransactionBroadcast((String,)), + Donation, + EstimateFee((u16,)), + HeadersSubscribe, + MempoolFeeHistogram, + PeersSubscribe, + Ping, + RelayFee, + ScriptHashGetHistory((ScriptHash,)), + ScriptHashSubscribe((ScriptHash,)), + TransactionGet(TxGetArgs), + TransactionGetMerkle((Txid, usize)), + Version((String, Version)), +} + +impl Call { + fn parse(method: &str, params: Value) -> std::result::Result { + Ok(match method { + "blockchain.block.header" => Call::BlockHeader(convert(params)?), + "blockchain.block.headers" => Call::BlockHeaders(convert(params)?), + "blockchain.estimatefee" => Call::EstimateFee(convert(params)?), + "blockchain.headers.subscribe" => Call::HeadersSubscribe, + "blockchain.relayfee" => Call::RelayFee, + "blockchain.scripthash.get_history" => Call::ScriptHashGetHistory(convert(params)?), + "blockchain.scripthash.subscribe" => Call::ScriptHashSubscribe(convert(params)?), + "blockchain.transaction.broadcast" => Call::TransactionBroadcast(convert(params)?), + "blockchain.transaction.get" => Call::TransactionGet(convert(params)?), + "blockchain.transaction.get_merkle" => Call::TransactionGetMerkle(convert(params)?), + "mempool.get_fee_histogram" => Call::MempoolFeeHistogram, + "server.banner" => Call::Banner, + "server.donation_address" => Call::Donation, + "server.peers.subscribe" => Call::PeersSubscribe, + "server.ping" => Call::Ping, + "server.version" => Call::Version(convert(params)?), + _ => { + warn!("unknown method {}", method); + return Err(StandardError::MethodNotFound); + } + }) + } +} + +fn convert(params: Value) -> std::result::Result +where + T: serde::de::DeserializeOwned, +{ + let params_str = params.to_string(); + serde_json::from_value(params).map_err(|err| { + warn!("invalid params {}: {}", params_str, err); + StandardError::InvalidParams + }) } fn notification(method: &str, params: &[Value]) -> Value { json!({"jsonrpc": "2.0", "method": method, "params": params}) } + +fn result_msg(id: Value, result: Value) -> Value { + json!({"jsonrpc": "2.0", "id": id, "result": result}) +} + +fn error_msg(id: Value, error: RpcError) -> Value { + json!({"jsonrpc": "2.0", "id": id, "error": error.to_value()}) +} diff --git a/src/server.rs b/src/server.rs index e415c9e..5d479bb 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,7 +3,6 @@ use bitcoin::BlockHash; use bitcoincore_rpc::RpcApi; use crossbeam_channel::{bounded, select, unbounded, Receiver, Sender}; use rayon::prelude::*; -use serde_json::{de::from_str, Value}; use std::{ collections::hash_map::HashMap, @@ -46,6 +45,17 @@ impl Peer { Self { id, client, stream } } + fn send(&mut self, values: Vec) -> Result<()> { + for mut value in values { + debug!("{}: send {}", self.id, value); + value += "\n"; + self.stream + .write_all(value.as_bytes()) + .with_context(|| format!("failed to send response: {:?}", value))?; + } + Ok(()) + } + fn disconnect(self) { let _ = self.stream.shutdown(Shutdown::Both); } @@ -123,7 +133,8 @@ fn notify_peer(rpc: &Rpc, peer: &mut Peer) -> Result<()> { let notifications = rpc .update_client(&mut peer.client) .context("failed to generate notifications")?; - send_to_peer(peer, ¬ifications).context("failed to send notifications") + peer.send(notifications) + .context("failed to send notifications") } struct Event { @@ -146,7 +157,7 @@ fn handle_event(rpc: &Rpc, peers: &mut HashMap, event: Event) { } Message::Request(line) => { let result = match peers.get_mut(&peer_id) { - Some(peer) => handle_request(rpc, peer, line), + Some(peer) => handle_request(rpc, peer, &line), None => return, // unknown peer }; if let Err(e) = result { @@ -161,24 +172,9 @@ fn handle_event(rpc: &Rpc, peers: &mut HashMap, event: Event) { } } -fn handle_request(rpc: &Rpc, peer: &mut Peer, line: String) -> Result<()> { - let request: Value = from_str(&line).with_context(|| format!("invalid request: {}", line))?; - let response: Value = rpc - .handle_request(&mut peer.client, request) - .with_context(|| format!("failed to handle request: {}", line))?; - send_to_peer(peer, &[response]) -} - -fn send_to_peer(peer: &mut Peer, values: &[Value]) -> Result<()> { - for value in values { - let mut response = value.to_string(); - debug!("{}: send {}", peer.id, response); - response += "\n"; - peer.stream - .write_all(response.as_bytes()) - .with_context(|| format!("failed to send response: {}", response))?; - } - Ok(()) +fn handle_request(rpc: &Rpc, peer: &mut Peer, line: &str) -> Result<()> { + let response = rpc.handle_request(&mut peer.client, &line); + peer.send(vec![response]) } fn accept_loop(listener: TcpListener, server_tx: Sender) -> Result<()> { From bb5caadb6b9126b1b2ebf53330ced5b89481c582 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 29 Jun 2021 20:51:42 +0300 Subject: [PATCH 047/113] Move p2p-related code out of daemon.rs --- src/daemon.rs | 152 ++---------------------------------------------- src/lib.rs | 1 + src/p2p.rs | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 147 deletions(-) create mode 100644 src/p2p.rs diff --git a/src/daemon.rs b/src/daemon.rs index 61540ff..ffc7732 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,90 +1,15 @@ use anyhow::{Context, Result}; -use std::io::Write; -use std::iter::FromIterator; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; -use std::time::{SystemTime, UNIX_EPOCH}; - -use bitcoin::consensus::encode; -use bitcoin::network::stream_reader::StreamReader; -use bitcoin::network::{ - address, constants, - message::{self, NetworkMessage}, - message_blockdata::{GetHeadersMessage, Inventory}, - message_network, -}; -use bitcoin::secp256k1; -use bitcoin::secp256k1::rand::Rng; -use bitcoin::{Amount, Block, BlockHash, Network, Transaction, Txid}; +use bitcoin::{Amount, Block, BlockHash, Transaction, Txid}; use bitcoincore_rpc::{self, json, RpcApi}; use parking_lot::Mutex; use crate::{ chain::{Chain, NewHeader}, config::Config, + p2p::Connection, }; -struct Connection { - stream: TcpStream, - reader: StreamReader, - network: Network, -} - -impl Connection { - pub fn connect(network: Network, address: SocketAddr) -> Result { - let stream = TcpStream::connect(address) - .with_context(|| format!("{} p2p failed to connect: {:?}", network, address))?; - let reader = StreamReader::new( - stream.try_clone().context("stream failed to clone")?, - /*buffer_size*/ Some(1 << 20), - ); - let mut conn = Self { - stream, - reader, - network, - }; - conn.send(build_version_message())?; - if let NetworkMessage::GetHeaders(_) = conn.recv().context("failed to get headers")? { - conn.send(NetworkMessage::Headers(vec![]))?; - } - Ok(conn) - } - - fn send(&mut self, msg: NetworkMessage) -> Result<()> { - trace!("send: {:?}", msg); - let raw_msg = message::RawNetworkMessage { - magic: self.network.magic(), - payload: msg, - }; - self.stream - .write_all(encode::serialize(&raw_msg).as_slice()) - .context("p2p failed to send") - } - - fn recv(&mut self) -> Result { - loop { - let raw_msg: message::RawNetworkMessage = - self.reader.read_next().context("p2p failed to recv")?; - - trace!("recv: {:?}", raw_msg.payload); - match raw_msg.payload { - NetworkMessage::Version(version) => { - debug!("peer version: {:?}", version); - self.send(NetworkMessage::Verack)?; - } - NetworkMessage::Ping(nonce) => { - self.send(NetworkMessage::Pong(nonce))?; - } - NetworkMessage::Verack - | NetworkMessage::Alert(_) - | NetworkMessage::Addr(_) - | NetworkMessage::Inv(_) => {} - payload => return Ok(payload), - }; - } - } -} - enum PollResult { Done(Result<()>), Retry, @@ -235,85 +160,18 @@ impl Daemon { } pub(crate) fn get_new_headers(&self, chain: &Chain) -> Result> { - let mut conn = self.p2p.lock(); - - let msg = GetHeadersMessage::new(chain.locator(), BlockHash::default()); - conn.send(NetworkMessage::GetHeaders(msg))?; - let headers = match conn.recv().context("failed to get new headers")? { - NetworkMessage::Headers(headers) => headers, - msg => bail!("unexpected {:?}", msg), - }; - - debug!("got {} new headers", headers.len()); - let prev_blockhash = match headers.first().map(|h| h.prev_blockhash) { - None => return Ok(vec![]), - Some(prev_blockhash) => prev_blockhash, - }; - let new_heights = match chain.get_block_height(&prev_blockhash) { - Some(last_height) => (last_height + 1).., - None => bail!("missing prev_blockhash: {}", prev_blockhash), - }; - Ok(headers - .into_iter() - .zip(new_heights) - .map(NewHeader::from) - .collect()) + self.p2p.lock().get_new_headers(chain) } - pub(crate) fn for_blocks(&self, blockhashes: B, mut func: F) -> Result<()> + pub(crate) fn for_blocks(&self, blockhashes: B, func: F) -> Result<()> where B: IntoIterator, F: FnMut(BlockHash, Block), { - let blockhashes = Vec::from_iter(blockhashes); - if blockhashes.is_empty() { - return Ok(()); - } - let inv = blockhashes - .iter() - .map(|h| Inventory::WitnessBlock(*h)) - .collect(); - debug!("loading {} blocks", blockhashes.len()); - let mut conn = self.p2p.lock(); - conn.send(NetworkMessage::GetData(inv))?; - for hash in blockhashes { - match conn - .recv() - .with_context(|| format!("failed to get block {}", hash))? - { - NetworkMessage::Block(block) => { - assert_eq!(block.block_hash(), hash, "got unexpected block"); - func(hash, block); - } - msg => bail!("unexpected {:?}", msg), - }; - } - Ok(()) + self.p2p.lock().for_blocks(blockhashes, func) } } -fn build_version_message() -> NetworkMessage { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time error") - .as_secs() as i64; - - let services = constants::ServiceFlags::NETWORK | constants::ServiceFlags::WITNESS; - - NetworkMessage::Version(message_network::VersionMessage { - version: constants::PROTOCOL_VERSION, - services, - timestamp, - receiver: address::Address::new(&addr, services), - sender: address::Address::new(&addr, services), - nonce: secp256k1::rand::thread_rng().gen(), - user_agent: String::from("electrs"), - start_height: 0, - relay: false, - }) -} - pub(crate) type RpcError = bitcoincore_rpc::jsonrpc::error::RpcError; pub(crate) fn extract_bitcoind_error(err: &bitcoincore_rpc::Error) -> Option<&RpcError> { diff --git a/src/lib.rs b/src/lib.rs index d0df5d5..8d84c2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ mod index; mod mempool; mod merkle; mod metrics; +mod p2p; pub mod server; mod signals; mod status; diff --git a/src/p2p.rs b/src/p2p.rs new file mode 100644 index 0000000..f43eb76 --- /dev/null +++ b/src/p2p.rs @@ -0,0 +1,156 @@ +use std::io::Write; +use std::iter::FromIterator; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; +use std::time::{SystemTime, UNIX_EPOCH}; + +use crate::chain::{Chain, NewHeader}; +use anyhow::{Context, Result}; +use bitcoin::{ + consensus::encode, + network::{ + address, constants, + message::{self, NetworkMessage}, + message_blockdata::{GetHeadersMessage, Inventory}, + message_network, + stream_reader::StreamReader, + }, + secp256k1::{self, rand::Rng}, + Block, BlockHash, Network, +}; + +pub(crate) struct Connection { + stream: TcpStream, + reader: StreamReader, + network: Network, +} + +impl Connection { + pub fn connect(network: Network, address: SocketAddr) -> Result { + let stream = TcpStream::connect(address) + .with_context(|| format!("{} p2p failed to connect: {:?}", network, address))?; + let reader = StreamReader::new( + stream.try_clone().context("stream failed to clone")?, + /*buffer_size*/ Some(1 << 20), + ); + let mut conn = Self { + stream, + reader, + network, + }; + conn.send(build_version_message())?; + if let NetworkMessage::GetHeaders(_) = conn.recv().context("failed to get headers")? { + conn.send(NetworkMessage::Headers(vec![]))?; + } + Ok(conn) + } + + fn send(&mut self, msg: NetworkMessage) -> Result<()> { + trace!("send: {:?}", msg); + let raw_msg = message::RawNetworkMessage { + magic: self.network.magic(), + payload: msg, + }; + self.stream + .write_all(encode::serialize(&raw_msg).as_slice()) + .context("p2p failed to send") + } + + fn recv(&mut self) -> Result { + loop { + let raw_msg: message::RawNetworkMessage = + self.reader.read_next().context("p2p failed to recv")?; + + trace!("recv: {:?}", raw_msg.payload); + match raw_msg.payload { + NetworkMessage::Version(version) => { + debug!("peer version: {:?}", version); + self.send(NetworkMessage::Verack)?; + } + NetworkMessage::Ping(nonce) => { + self.send(NetworkMessage::Pong(nonce))?; + } + NetworkMessage::Verack + | NetworkMessage::Alert(_) + | NetworkMessage::Addr(_) + | NetworkMessage::Inv(_) => {} + payload => return Ok(payload), + }; + } + } + + pub(crate) fn for_blocks(&mut self, blockhashes: B, mut func: F) -> Result<()> + where + B: IntoIterator, + F: FnMut(BlockHash, Block), + { + let blockhashes = Vec::from_iter(blockhashes); + if blockhashes.is_empty() { + return Ok(()); + } + let inv = blockhashes + .iter() + .map(|h| Inventory::WitnessBlock(*h)) + .collect(); + debug!("loading {} blocks", blockhashes.len()); + self.send(NetworkMessage::GetData(inv))?; + for hash in blockhashes { + match self + .recv() + .with_context(|| format!("failed to get block {}", hash))? + { + NetworkMessage::Block(block) => { + assert_eq!(block.block_hash(), hash, "got unexpected block"); + func(hash, block); + } + msg => bail!("unexpected {:?}", msg), + }; + } + Ok(()) + } + + pub(crate) fn get_new_headers(&mut self, chain: &Chain) -> Result> { + let msg = GetHeadersMessage::new(chain.locator(), BlockHash::default()); + self.send(NetworkMessage::GetHeaders(msg))?; + let headers = match self.recv().context("failed to get new headers")? { + NetworkMessage::Headers(headers) => headers, + msg => bail!("unexpected {:?}", msg), + }; + + debug!("got {} new headers", headers.len()); + let prev_blockhash = match headers.first().map(|h| h.prev_blockhash) { + None => return Ok(vec![]), + Some(prev_blockhash) => prev_blockhash, + }; + let new_heights = match chain.get_block_height(&prev_blockhash) { + Some(last_height) => (last_height + 1).., + None => bail!("missing prev_blockhash: {}", prev_blockhash), + }; + Ok(headers + .into_iter() + .zip(new_heights) + .map(NewHeader::from) + .collect()) + } +} + +fn build_version_message() -> NetworkMessage { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time error") + .as_secs() as i64; + + let services = constants::ServiceFlags::NETWORK | constants::ServiceFlags::WITNESS; + + NetworkMessage::Version(message_network::VersionMessage { + version: constants::PROTOCOL_VERSION, + services, + timestamp, + receiver: address::Address::new(&addr, services), + sender: address::Address::new(&addr, services), + nonce: secp256k1::rand::thread_rng().gen(), + user_agent: String::from("electrs"), + start_height: 0, + relay: false, + }) +} From 4d2481ed452d2dc76afe5b73d07a6c483ac1ef5c Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 30 Jun 2021 21:27:59 +0300 Subject: [PATCH 048/113] Allow calling blockchain.scripthash.get_history without subscription Fixes #419 --- src/electrum.rs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index 8a0bec9..e12cc5e 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -223,14 +223,17 @@ impl Rpc { client: &Client, (scripthash,): (ScriptHash,), ) -> Result { - let status = client - .status - .get(&scripthash) - .context("no subscription for scripthash")?; - Ok(json!(self - .tracker - .get_history(status) - .collect::>())) + let history_entries = match client.status.get(&scripthash) { + Some(status) => self.tracker.get_history(status), + None => { + warn!( + "blockchain.scripthash.get_history called for unsubscribed scripthash: {}", + scripthash + ); + self.tracker.get_history(&self.new_status(scripthash)?) + } + }; + Ok(json!(history_entries.collect::>())) } fn scripthash_subscribe( @@ -238,14 +241,19 @@ impl Rpc { client: &mut Client, (scripthash,): (ScriptHash,), ) -> Result { - let mut status = Status::new(scripthash); - self.tracker - .update_status(&mut status, &self.daemon, &self.cache)?; + let status = self.new_status(scripthash)?; let statushash = status.statushash(); client.status.insert(scripthash, status); // skip if already exists Ok(json!(statushash)) } + fn new_status(&self, scripthash: ScriptHash) -> Result { + let mut status = Status::new(scripthash); + self.tracker + .update_status(&mut status, &self.daemon, &self.cache)?; + Ok(status) + } + fn transaction_broadcast(&self, (tx_hex,): (String,)) -> Result { let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?; let tx = deserialize(&tx_bytes).context("invalid transaction")?; From c754485320d8fe3f6ebe13f41b0d2bd334f963a6 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 30 Jun 2021 21:44:29 +0300 Subject: [PATCH 049/113] Add blockchain.scripthash.get_balance RPC --- contrib/history.py | 16 +++++- src/bin/query.rs | 8 +-- src/electrum.rs | 24 +++++++++ src/lib.rs | 2 +- src/status.rs | 131 ++++++++++++++++++++++++++++++++------------- src/tracker.rs | 20 ++++--- 6 files changed, 146 insertions(+), 55 deletions(-) diff --git a/contrib/history.py b/contrib/history.py index 3b5dd90..be92c64 100755 --- a/contrib/history.py +++ b/contrib/history.py @@ -47,10 +47,10 @@ def main(): tip, = conn.call([client.request('blockchain.headers.subscribe')]) - script_hashes = set( + script_hashes = [ _script_hash(network.parse.address(addr).script()) for addr in args.address - ) + ] conn.call( client.request('blockchain.scripthash.subscribe', script_hash) @@ -58,6 +58,17 @@ def main(): ) log.info('subscribed to {} scripthashes', len(script_hashes)) + balances = conn.call( + client.request('blockchain.scripthash.get_balance', script_hash) + for script_hash in script_hashes + ) + balances = [balance["confirmed"] for balance in balances] + total = sum(balances) + for balance, addr in sorted(zip(balances, args.address)): + if balance: + log.debug('{:15,.5f} mBTC at {}', balance / 1e5, addr) + log.debug('{:15,.5f} mBTC (total)', total / 1e5) + histories = conn.call( client.request('blockchain.scripthash.get_history', script_hash) for script_hash in script_hashes @@ -106,6 +117,7 @@ def main(): balance = 0 rows = [] + script_hashes = set(script_hashes) for block_height, block_pos, txid in sorted_txdata: tx_obj = txs_map[txid] for txi in tx_obj.txs_in: diff --git a/src/bin/query.rs b/src/bin/query.rs index 7287859..78ac02b 100644 --- a/src/bin/query.rs +++ b/src/bin/query.rs @@ -7,7 +7,7 @@ use bitcoin::{Address, Amount}; use std::collections::BTreeMap; use std::str::FromStr; -use electrs::{Cache, Config, Daemon, ScriptHash, Status, Tracker}; +use electrs::{Balance, Cache, Config, Daemon, ScriptHash, Status, Tracker}; fn main() -> Result<()> { let config = Config::from_args(); @@ -32,10 +32,10 @@ fn main() -> Result<()> { for (addr, status) in map.iter_mut() { tracker.update_status(status, &daemon, &cache)?; let balance = tracker.get_balance(status, &cache); - if balance > Amount::ZERO { - info!("{} has {}", addr, balance); + if balance != Balance::default() { + info!("{} has {}", addr, balance.confirmed()); } - total += balance; + total += balance.confirmed(); } info!("total: {}", total); std::thread::sleep(config.wait_duration); diff --git a/src/electrum.rs b/src/electrum.rs index e12cc5e..00afa68 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -218,6 +218,27 @@ impl Rpc { Ok(json!(self.daemon.get_relay_fee()?.as_btc())) // [BTC/kB] } + fn scripthash_get_balance( + &self, + client: &Client, + (scripthash,): (ScriptHash,), + ) -> Result { + let balance = match client.status.get(&scripthash) { + Some(status) => self.tracker.get_balance(status, &self.cache), + None => { + warn!( + "blockchain.scripthash.get_balance called for unsubscribed scripthash: {}", + scripthash + ); + self.tracker + .get_balance(&self.new_status(scripthash)?, &self.cache) + } + }; + Ok( + json!({"confirmed": balance.confirmed.as_sat(), "unconfirmed": balance.mempool_delta.as_sat()}), + ) + } + fn scripthash_get_history( &self, client: &Client, @@ -367,6 +388,7 @@ impl Rpc { Call::PeersSubscribe => Ok(json!([])), Call::Ping => Ok(Value::Null), Call::RelayFee => self.relayfee(), + Call::ScriptHashGetBalance(args) => self.scripthash_get_balance(client, args), Call::ScriptHashGetHistory(args) => self.scripthash_get_history(client, args), Call::ScriptHashSubscribe(args) => self.scripthash_subscribe(client, args), Call::TransactionBroadcast(args) => self.transaction_broadcast(args), @@ -404,6 +426,7 @@ enum Call { PeersSubscribe, Ping, RelayFee, + ScriptHashGetBalance((ScriptHash,)), ScriptHashGetHistory((ScriptHash,)), ScriptHashSubscribe((ScriptHash,)), TransactionGet(TxGetArgs), @@ -419,6 +442,7 @@ impl Call { "blockchain.estimatefee" => Call::EstimateFee(convert(params)?), "blockchain.headers.subscribe" => Call::HeadersSubscribe, "blockchain.relayfee" => Call::RelayFee, + "blockchain.scripthash.get_balance" => Call::ScriptHashGetBalance(convert(params)?), "blockchain.scripthash.get_history" => Call::ScriptHashGetHistory(convert(params)?), "blockchain.scripthash.subscribe" => Call::ScriptHashSubscribe(convert(params)?), "blockchain.transaction.broadcast" => Call::TransactionBroadcast(convert(params)?), diff --git a/src/lib.rs b/src/lib.rs index 8d84c2b..7f3ebe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub use { config::Config, daemon::Daemon, electrum::{Client, Rpc}, - status::Status, + status::{Balance, Status}, tracker::Tracker, types::ScriptHash, }; diff --git a/src/status.rs b/src/status.rs index 6bca9de..fe665f5 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,7 +1,7 @@ use anyhow::Result; use bitcoin::{ hashes::{sha256, Hash, HashEngine}, - Amount, Block, BlockHash, OutPoint, Transaction, Txid, + Amount, Block, BlockHash, OutPoint, SignedAmount, Transaction, Txid, }; use rayon::prelude::*; use serde_json::{json, Value}; @@ -39,6 +39,10 @@ impl TxEntry { spent: entry.spent, } } + + fn funding<'a>(&'a self) -> impl Iterator + 'a { + make_outpoints(&self.txid, &self.outputs) + } } pub(crate) struct ConfirmedEntry { @@ -91,6 +95,48 @@ pub struct Status { mempool: Vec, } +enum BalanceEntry { + Funded(OutPoint), + Spent(OutPoint), +} + +#[derive(Default, Eq, PartialEq)] +pub struct Balance { + pub(crate) confirmed: Amount, + pub(crate) mempool_delta: SignedAmount, +} + +impl Balance { + pub fn confirmed(&self) -> Amount { + self.confirmed + } +} + +#[derive(Default)] +struct Total { + funded: Amount, + spent: Amount, +} + +impl Total { + fn balance(&self) -> Amount { + self.funded - self.spent + } + + fn update( + &mut self, + entries: impl Iterator, + get_amount: impl Fn(OutPoint) -> Amount, + ) { + for entry in entries { + match entry { + BalanceEntry::Funded(outpoint) => self.funded += get_amount(outpoint), + BalanceEntry::Spent(outpoint) => self.spent += get_amount(outpoint), + } + } + } +} + fn make_outpoints<'a>(txid: &'a Txid, outputs: &'a [u32]) -> impl Iterator + 'a { outputs.iter().map(move |vout| OutPoint::new(*txid, *vout)) } @@ -106,42 +152,10 @@ impl Status { } } - fn funding_confirmed(&self, chain: &Chain) -> HashSet { - self.confirmed - .iter() - .filter_map(|(blockhash, entries)| chain.get_block_height(blockhash).map(|_| entries)) - .flat_map(|entries| { - entries - .iter() - .flat_map(|entry| make_outpoints(&entry.txid, &entry.outputs)) - }) - .collect() - } - - pub(crate) fn get_unspent(&self, chain: &Chain) -> HashSet { - let mut unspent: HashSet = self.funding_confirmed(chain); - unspent.extend( - self.mempool - .iter() - .flat_map(|entry| make_outpoints(&entry.txid, &entry.outputs)), - ); - - let spent_outpoints = self - .confirmed - .iter() - .filter_map(|(blockhash, entries)| { - chain.get_block_height(blockhash).map(|_height| entries) - }) - .flatten() - .chain(self.mempool.iter()) - .flat_map(|entry| entry.spent.iter()); - for outpoint in spent_outpoints { - assert!(unspent.remove(outpoint), "missing outpoint {}", outpoint); - } - unspent - } - - pub(crate) fn get_confirmed(&self, chain: &Chain) -> Vec { + fn confirmed_entries<'a>( + &'a self, + chain: &'a Chain, + ) -> impl Iterator + 'a { self.confirmed .iter() .filter_map(move |(blockhash, entries)| { @@ -149,6 +163,49 @@ impl Status { .get_block_height(blockhash) .map(|height| (height, &entries[..])) }) + } + + fn funding_confirmed(&self, chain: &Chain) -> HashSet { + self.confirmed_entries(chain) + .flat_map(|(_height, entries)| entries.iter().flat_map(TxEntry::funding)) + .collect() + } + + pub(crate) fn get_balance(&self, chain: &Chain, get_amount: F) -> Balance + where + F: Fn(OutPoint) -> Amount, + { + fn to_balance_entries<'a>( + entries: impl Iterator + 'a, + ) -> impl Iterator + 'a { + entries.flat_map(|entry| { + let funded = entry.funding().map(BalanceEntry::Funded); + let spent = entry.spent.iter().copied().map(BalanceEntry::Spent); + funded.chain(spent) + }) + } + + let confirmed_entries = to_balance_entries( + self.confirmed_entries(chain) + .flat_map(|(_height, entries)| entries), + ); + let mempool_entries = to_balance_entries(self.mempool.iter()); + + let mut total = Total::default(); + total.update(confirmed_entries, &get_amount); + let confirmed = total.balance(); + + total.update(mempool_entries, &get_amount); + let with_mempool = total.balance(); + + Balance { + confirmed, + mempool_delta: with_mempool.to_signed().unwrap() - confirmed.to_signed().unwrap(), + } + } + + pub(crate) fn get_confirmed(&self, chain: &Chain) -> Vec { + self.confirmed_entries(chain) .collect::>() .into_iter() .flat_map(|(height, entries)| { diff --git a/src/tracker.rs b/src/tracker.rs index 2c00db1..0e92095 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use bitcoin::{BlockHash, Txid}; +use bitcoin::{BlockHash, OutPoint, Txid}; use serde_json::Value; use std::convert::TryInto; @@ -14,7 +14,7 @@ use crate::{ index::Index, mempool::{Histogram, Mempool}, metrics::Metrics, - status::Status, + status::{Balance, Status}, }; /// Electrum protocol subscriptions' tracker @@ -84,19 +84,17 @@ impl Tracker { Ok(prev_statushash != status.statushash()) } - pub fn get_balance(&self, status: &Status, cache: &Cache) -> bitcoin::Amount { - let unspent = status.get_unspent(&self.index.chain()); - let mut balance = bitcoin::Amount::ZERO; - for outpoint in &unspent { - let value = cache + pub fn get_balance(&self, status: &Status, cache: &Cache) -> Balance { + let get_amount_fn = |outpoint: OutPoint| { + cache .get_tx(&outpoint.txid, |tx| { let vout: usize = outpoint.vout.try_into().unwrap(); bitcoin::Amount::from_sat(tx.output[vout].value) }) - .expect("missing tx"); - balance += value; - } - balance + .expect("missing tx") + }; + + status.get_balance(self.chain(), get_amount_fn) } pub fn get_blockhash_by_txid(&self, txid: Txid) -> Option { From 11d703f5457ee0db6d2aaaaba5dd6b8051d1e653 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 4 Jul 2021 15:08:26 +0300 Subject: [PATCH 050/113] Remove unneeded into_iter() --- src/status.rs | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/status.rs b/src/status.rs index fe665f5..60e607a 100644 --- a/src/status.rs +++ b/src/status.rs @@ -280,27 +280,23 @@ impl Status { .par_iter() .flat_map_iter(|outpoint| index.filter_by_spending(*outpoint)) .collect(); - self.for_new_blocks( - spending_blockhashes.into_iter(), - daemon, - |blockhash, block| { - let txids: Vec = block.txdata.iter().map(|tx| tx.txid()).collect(); - for (pos, (tx, txid)) in block.txdata.into_iter().zip(txids.iter()).enumerate() { - let spent_outpoints = filter_inputs(&tx, &outpoints); - if spent_outpoints.is_empty() { - continue; - } - cache.add_tx(*txid, move || tx); - cache.add_proof(blockhash, *txid, || Proof::create(&txids, pos)); - result - .entry(blockhash) - .or_default() - .entry((u32::try_from(pos).unwrap(), *txid)) - .or_default() - .spent = spent_outpoints; + self.for_new_blocks(spending_blockhashes, daemon, |blockhash, block| { + let txids: Vec = block.txdata.iter().map(|tx| tx.txid()).collect(); + for (pos, (tx, txid)) in block.txdata.into_iter().zip(txids.iter()).enumerate() { + let spent_outpoints = filter_inputs(&tx, &outpoints); + if spent_outpoints.is_empty() { + continue; } - }, - )?; + cache.add_tx(*txid, move || tx); + cache.add_proof(blockhash, *txid, || Proof::create(&txids, pos)); + result + .entry(blockhash) + .or_default() + .entry((u32::try_from(pos).unwrap(), *txid)) + .or_default() + .spent = spent_outpoints; + } + })?; Ok(result .into_iter() From 3bf5e5c266ecdbd10aa7709e495113521256e9f1 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 16 Jul 2021 16:09:36 +0300 Subject: [PATCH 051/113] Return original JSON from getrawtransaction RPC Should fix https://github.com/romanz/electrs/issues/427 --- src/daemon.rs | 9 +++++++-- src/electrum.rs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index ffc7732..5cfe4ee 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -3,6 +3,7 @@ use anyhow::{Context, Result}; use bitcoin::{Amount, Block, BlockHash, Transaction, Txid}; use bitcoincore_rpc::{self, json, RpcApi}; use parking_lot::Mutex; +use serde_json::{json, Value}; use crate::{ chain::{Chain, NewHeader}, @@ -113,9 +114,13 @@ impl Daemon { &self, txid: &Txid, blockhash: Option, - ) -> Result { + ) -> Result { + // No need to parse the resulting JSON, just return it as-is to the client. self.rpc - .get_raw_transaction_info(txid, blockhash.as_ref()) + .call( + "getrawtransaction", + &[json!(txid), json!(true), json!(blockhash)], + ) .context("failed to get transaction info") } diff --git a/src/electrum.rs b/src/electrum.rs index 00afa68..5dae799 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -286,7 +286,7 @@ impl Rpc { let (txid, verbose) = args.into(); if verbose { let blockhash = self.tracker.get_blockhash_by_txid(txid); - return Ok(json!(self.daemon.get_transaction_info(&txid, blockhash)?)); + return self.daemon.get_transaction_info(&txid, blockhash); } let cached = self.cache.get_tx(&txid, |tx| serialize(tx).to_hex()); Ok(match cached { From 2510531d16c0174b9612e703d4c57310ecca3b5a Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 20 Jul 2021 18:56:43 +0300 Subject: [PATCH 052/113] Fixup some nightly clippy lints --- src/config.rs | 8 ++++---- src/electrum.rs | 2 +- src/index.rs | 14 +++++++------- src/server.rs | 8 ++++---- src/status.rs | 8 ++++---- src/tracker.rs | 4 ++-- src/types.rs | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/config.rs b/src/config.rs index 41a81a7..ff5ab09 100644 --- a/src/config.rs +++ b/src/config.rs @@ -71,7 +71,7 @@ impl ResolvAddr { /// Resolves the address. fn resolve(self) -> std::result::Result { match self.0.to_socket_addrs() { - Ok(mut iter) => iter.next().ok_or_else(|| AddressError::NoAddrError(self.0)), + Ok(mut iter) => iter.next().ok_or(AddressError::NoAddrError(self.0)), Err(err) => Err(AddressError::ResolvError { addr: self.0, err }), } } @@ -109,9 +109,9 @@ impl ::configure_me::parse_arg::ParseArgFromStr for BitcoinNetwork { } } -impl Into for BitcoinNetwork { - fn into(self) -> Network { - self.0 +impl From for Network { + fn from(network: BitcoinNetwork) -> Network { + network.0 } } diff --git a/src/electrum.rs b/src/electrum.rs index 5dae799..ba182bb 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -128,7 +128,7 @@ impl Rpc { tracker, cache: Cache::default(), rpc_duration, - daemon: Daemon::connect(&config)?, + daemon: Daemon::connect(config)?, banner: config.server_banner.clone(), }) } diff --git a/src/index.rs b/src/index.rs index 730c66d..ade0777 100644 --- a/src/index.rs +++ b/src/index.rs @@ -112,27 +112,27 @@ impl Index { &self.chain } - pub(crate) fn filter_by_txid<'a>(&'a self, txid: Txid) -> impl Iterator + 'a { + pub(crate) fn filter_by_txid(&self, txid: Txid) -> impl Iterator + '_ { self.store .iter_txid(TxidRow::scan_prefix(txid)) .map(|row| TxidRow::from_db_row(&row).height()) .filter_map(move |height| self.chain.get_block_hash(height)) } - pub(crate) fn filter_by_funding<'a>( - &'a self, + pub(crate) fn filter_by_funding( + &self, scripthash: ScriptHash, - ) -> impl Iterator + 'a { + ) -> impl Iterator + '_ { self.store .iter_funding(ScriptHashRow::scan_prefix(scripthash)) .map(|row| ScriptHashRow::from_db_row(&row).height()) .filter_map(move |height| self.chain.get_block_hash(height)) } - pub(crate) fn filter_by_spending<'a>( - &'a self, + pub(crate) fn filter_by_spending( + &self, outpoint: OutPoint, - ) -> impl Iterator + 'a { + ) -> impl Iterator + '_ { self.store .iter_spending(SpendingPrefixRow::scan_prefix(outpoint)) .map(|row| SpendingPrefixRow::from_db_row(&row).height()) diff --git a/src/server.rs b/src/server.rs index 5d479bb..4dd0460 100644 --- a/src/server.rs +++ b/src/server.rs @@ -64,7 +64,7 @@ impl Peer { fn tip_receiver(config: &Config) -> Result> { let duration = u64::try_from(config.wait_duration.as_millis()).unwrap(); let (tip_tx, tip_rx) = bounded(0); - let rpc = rpc_connect(&config)?; + let rpc = rpc_connect(config)?; use crossbeam_channel::TrySendError; spawn("tip_loop", move || loop { @@ -80,7 +80,7 @@ fn tip_receiver(config: &Config) -> Result> { pub fn run(config: &Config, mut rpc: Rpc) -> Result<()> { let listener = TcpListener::bind(config.electrum_rpc_addr)?; - let tip_rx = tip_receiver(&config)?; + let tip_rx = tip_receiver(config)?; info!("serving Electrum RPC on {}", listener.local_addr()?); let (server_tx, server_rx) = unbounded(); @@ -118,7 +118,7 @@ pub fn run(config: &Config, mut rpc: Rpc) -> Result<()> { fn notify_peers(rpc: &Rpc, peers: HashMap) -> HashMap { peers .into_par_iter() - .filter_map(|(_, mut peer)| match notify_peer(&rpc, &mut peer) { + .filter_map(|(_, mut peer)| match notify_peer(rpc, &mut peer) { Ok(()) => Some((peer.id, peer)), Err(e) => { error!("failed to notify peer {}: {}", peer.id, e); @@ -173,7 +173,7 @@ fn handle_event(rpc: &Rpc, peers: &mut HashMap, event: Event) { } fn handle_request(rpc: &Rpc, peer: &mut Peer, line: &str) -> Result<()> { - let response = rpc.handle_request(&mut peer.client, &line); + let response = rpc.handle_request(&mut peer.client, line); peer.send(vec![response]) } diff --git a/src/status.rs b/src/status.rs index 60e607a..d2bd588 100644 --- a/src/status.rs +++ b/src/status.rs @@ -40,7 +40,7 @@ impl TxEntry { } } - fn funding<'a>(&'a self) -> impl Iterator + 'a { + fn funding(&self) -> impl Iterator + '_ { make_outpoints(&self.txid, &self.outputs) } } @@ -267,7 +267,7 @@ impl Status { } cache.add_tx(*txid, move || tx); cache.add_proof(blockhash, *txid, || Proof::create(&txids, pos)); - outpoints.extend(make_outpoints(&txid, &funding_outputs)); + outpoints.extend(make_outpoints(txid, &funding_outputs)); result .entry(blockhash) .or_default() @@ -283,7 +283,7 @@ impl Status { self.for_new_blocks(spending_blockhashes, daemon, |blockhash, block| { let txids: Vec = block.txdata.iter().map(|tx| tx.txid()).collect(); for (pos, (tx, txid)) in block.txdata.into_iter().zip(txids.iter()).enumerate() { - let spent_outpoints = filter_inputs(&tx, &outpoints); + let spent_outpoints = filter_inputs(&tx, outpoints); if spent_outpoints.is_empty() { continue; } @@ -330,7 +330,7 @@ impl Status { .iter() .flat_map(|outpoint| mempool.filter_by_spending(outpoint)) { - let spent_outpoints = filter_inputs(&entry.tx, &outpoints); + let spent_outpoints = filter_inputs(&entry.tx, outpoints); assert!(!spent_outpoints.is_empty()); result.entry(entry.txid).or_default().spent = spent_outpoints; cache.add_tx(entry.txid, || entry.tx.clone()); diff --git a/src/tracker.rs b/src/tracker.rs index 0e92095..6574b4b 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -45,7 +45,7 @@ impl Tracker { } pub(crate) fn fees_histogram(&self) -> &Histogram { - &self.mempool.fees_histogram() + self.mempool.fees_histogram() } pub(crate) fn metrics(&self) -> &Metrics { @@ -54,7 +54,7 @@ impl Tracker { pub fn get_history(&self, status: &Status) -> impl Iterator { let confirmed = status - .get_confirmed(&self.index.chain()) + .get_confirmed(self.index.chain()) .into_iter() .map(|entry| entry.value()); let mempool = status diff --git a/src/types.rs b/src/types.rs index 480f014..7151618 100644 --- a/src/types.rs +++ b/src/types.rs @@ -93,7 +93,7 @@ impl ScriptHashRow { } pub(crate) fn from_db_row(row: &[u8]) -> Self { - deserialize(&row).expect("bad ScriptHashRow") + deserialize(row).expect("bad ScriptHashRow") } pub(crate) fn height(&self) -> usize { @@ -154,7 +154,7 @@ impl SpendingPrefixRow { } pub(crate) fn from_db_row(row: &[u8]) -> Self { - deserialize(&row).expect("bad SpendingPrefixRow") + deserialize(row).expect("bad SpendingPrefixRow") } pub(crate) fn height(&self) -> usize { @@ -202,7 +202,7 @@ impl TxidRow { } pub(crate) fn from_db_row(row: &[u8]) -> Self { - deserialize(&row).expect("bad TxidRow") + deserialize(row).expect("bad TxidRow") } pub(crate) fn height(&self) -> usize { @@ -229,7 +229,7 @@ impl HeaderRow { } pub(crate) fn from_db_row(row: &[u8]) -> Self { - deserialize(&row).expect("bad HeaderRow") + deserialize(row).expect("bad HeaderRow") } } From 3bb0fb7920f0d495e834cb71764b60ec86ca25ff Mon Sep 17 00:00:00 2001 From: Simon Vrouwe Date: Tue, 20 Jul 2021 12:47:32 +0300 Subject: [PATCH 053/113] p2p doc: in bitcoin.conf maxconnections should be >11, fixes #429 --- doc/usage.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/usage.md b/doc/usage.md index d29c4d3..98a22bb 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -193,7 +193,8 @@ If you use automated systems, refer to their documentation first! Pruning must be turned **off** for `electrs` to work. `txindex` is allowed but unnecessary for `electrs`. -However, you might still need it if you run other services (e.g.`eclair`) +However, you might still need it if you run other services (e.g.`eclair`). +The option `maxconnections` (if used) should be set to 12 or more for bitcoind to accept inbound p2p connections. The highly recommended way of authenticating `electrs` is using cookie file. It's the most [secure](https://github.com/Kixunil/security_writings/blob/master/cookie_files.md) and robust method. From 8ef15e25b9d24e16626a6157b07de9d716162dce Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 22 Jul 2021 17:12:40 +0300 Subject: [PATCH 054/113] Fix `--auth` support If not specified, use `--cookie-file`. --- src/config.rs | 24 +++++++++++++++++++----- src/daemon.rs | 9 +++++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index ff5ab09..3e39d41 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use bitcoin::network::constants::Network; +use bitcoincore_rpc::Auth; use dirs_next::home_dir; use std::ffi::{OsStr, OsString}; @@ -122,7 +123,7 @@ pub struct Config { pub network: Network, pub db_path: PathBuf, pub daemon_dir: PathBuf, - pub daemon_cookie_file: PathBuf, + pub daemon_auth: Auth, pub daemon_rpc_addr: SocketAddr, pub daemon_p2p_addr: SocketAddr, pub electrum_rpc_addr: SocketAddr, @@ -228,15 +229,28 @@ impl Config { } let daemon_dir = &config.daemon_dir; - let daemon_cookie_file = config - .cookie_file - .unwrap_or_else(|| daemon_dir.join(".cookie")); + let daemon_auth = match (config.auth, config.cookie_file) { + (None, None) => Auth::CookieFile(daemon_dir.join(".cookie")), + (None, Some(cookie_file)) => Auth::CookieFile(cookie_file), + (Some(auth), None) => { + let parts: Vec<&str> = auth.splitn(2, ":").collect(); + if parts.len() != 2 { + eprintln!("Error: auth cookie doesn't contain colon"); + std::process::exit(1); + } + Auth::UserPass(parts[0].to_owned(), parts[1].to_owned()) + } + (Some(_), Some(_)) => { + eprintln!("Error: ambigous configuration - auth and cookie_file can't be specified at the same time"); + std::process::exit(1); + } + }; let config = Config { network: config.network, db_path: config.db_dir, daemon_dir: config.daemon_dir, - daemon_cookie_file, + daemon_auth, daemon_rpc_addr, daemon_p2p_addr, electrum_rpc_addr, diff --git a/src/daemon.rs b/src/daemon.rs index 5cfe4ee..9ded907 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -48,11 +48,12 @@ fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { pub(crate) fn rpc_connect(config: &Config) -> Result { let rpc_url = format!("http://{}", config.daemon_rpc_addr); - if !config.daemon_cookie_file.exists() { - bail!("{:?} is missing", config.daemon_cookie_file); + if let bitcoincore_rpc::Auth::CookieFile(ref path) = config.daemon_auth { + if !path.exists() { + bail!("{:?} is missing - is bitcoind running?", path); + } } - let rpc_auth = bitcoincore_rpc::Auth::CookieFile(config.daemon_cookie_file.clone()); - let mut client = bitcoincore_rpc::Client::new(rpc_url, rpc_auth) + let mut client = bitcoincore_rpc::Client::new(rpc_url, config.daemon_auth.clone()) .with_context(|| format!("failed to connect to RPC: {}", config.daemon_rpc_addr))?; loop { From 5f0a10e517521938373284e27c405b1f5ac30e77 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 22 Jul 2021 21:48:49 +0300 Subject: [PATCH 055/113] Don't log RPC password --- src/config.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++---- src/daemon.rs | 5 +++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index 3e39d41..0ff4d50 100644 --- a/src/config.rs +++ b/src/config.rs @@ -105,7 +105,7 @@ impl FromStr for BitcoinNetwork { } impl ::configure_me::parse_arg::ParseArgFromStr for BitcoinNetwork { - fn describe_type(mut writer: W) -> std::fmt::Result { + fn describe_type(mut writer: W) -> fmt::Result { write!(writer, "either 'bitcoin', 'testnet', 'regtest' or 'signet'") } } @@ -123,7 +123,7 @@ pub struct Config { pub network: Network, pub db_path: PathBuf, pub daemon_dir: PathBuf, - pub daemon_auth: Auth, + pub daemon_auth: SensitiveAuth, pub daemon_rpc_addr: SocketAddr, pub daemon_p2p_addr: SocketAddr, pub electrum_rpc_addr: SocketAddr, @@ -135,6 +135,27 @@ pub struct Config { pub args: Vec, } +pub struct SensitiveAuth(pub Auth); + +impl SensitiveAuth { + pub(crate) fn get_auth(&self) -> Auth { + self.0.clone() + } +} + +impl fmt::Debug for SensitiveAuth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Auth::UserPass(ref user, _) => f + .debug_tuple("UserPass") + .field(&user) + .field(&"") + .finish(), + _ => write!(f, "{:?}", self.0), + } + } +} + /// Returns default daemon directory fn default_daemon_dir() -> PathBuf { let mut home = home_dir().unwrap_or_else(|| { @@ -229,7 +250,7 @@ impl Config { } let daemon_dir = &config.daemon_dir; - let daemon_auth = match (config.auth, config.cookie_file) { + let daemon_auth = SensitiveAuth(match (config.auth, config.cookie_file) { (None, None) => Auth::CookieFile(daemon_dir.join(".cookie")), (None, Some(cookie_file)) => Auth::CookieFile(cookie_file), (Some(auth), None) => { @@ -244,7 +265,7 @@ impl Config { eprintln!("Error: ambigous configuration - auth and cookie_file can't be specified at the same time"); std::process::exit(1); } - }; + }); let config = Config { network: config.network, @@ -269,3 +290,27 @@ impl Config { config } } + +#[cfg(test)] +mod tests { + use super::{Auth, SensitiveAuth}; + use std::path::Path; + + #[test] + fn test_auth_debug() { + let auth = Auth::None; + assert_eq!(format!("{:?}", SensitiveAuth(auth)), "None"); + + let auth = Auth::CookieFile(Path::new("/foo/bar/.cookie").to_path_buf()); + assert_eq!( + format!("{:?}", SensitiveAuth(auth)), + "CookieFile(\"/foo/bar/.cookie\")" + ); + + let auth = Auth::UserPass("user".to_owned(), "pass".to_owned()); + assert_eq!( + format!("{:?}", SensitiveAuth(auth)), + "UserPass(\"user\", \"\")" + ); + } +} diff --git a/src/daemon.rs b/src/daemon.rs index 9ded907..c3b008d 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -48,12 +48,13 @@ fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { pub(crate) fn rpc_connect(config: &Config) -> Result { let rpc_url = format!("http://{}", config.daemon_rpc_addr); - if let bitcoincore_rpc::Auth::CookieFile(ref path) = config.daemon_auth { + let auth = config.daemon_auth.get_auth(); + if let bitcoincore_rpc::Auth::CookieFile(ref path) = auth { if !path.exists() { bail!("{:?} is missing - is bitcoind running?", path); } } - let mut client = bitcoincore_rpc::Client::new(rpc_url, config.daemon_auth.clone()) + let mut client = bitcoincore_rpc::Client::new(rpc_url, auth) .with_context(|| format!("failed to connect to RPC: {}", config.daemon_rpc_addr))?; loop { From 18a467b9aa8f5deedc6f9e1567991cab122a7cbf Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 23 Jul 2021 11:11:39 +0300 Subject: [PATCH 056/113] Add server.features RPC --- src/electrum.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index ba182bb..3277c40 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -116,6 +116,7 @@ pub struct Rpc { rpc_duration: Histogram, daemon: Daemon, banner: String, + port: u16, } impl Rpc { @@ -130,6 +131,7 @@ impl Rpc { rpc_duration, daemon: Daemon::connect(config)?, banner: config.server_banner.clone(), + port: config.electrum_rpc_addr.port(), }) } @@ -327,11 +329,14 @@ impl Rpc { Ok(json!(self.tracker.fees_histogram())) } + fn server_id(&self) -> String { + format!("electrs/{}", ELECTRS_VERSION) + } + fn version(&self, (client_id, client_version): (String, Version)) -> Result { match client_version { Version::Single(v) if v == PROTOCOL_VERSION => { - let server_id = format!("electrs/{}", ELECTRS_VERSION); - Ok(json!([server_id, PROTOCOL_VERSION])) + Ok(json!([self.server_id(), PROTOCOL_VERSION])) } _ => { bail!( @@ -344,6 +349,18 @@ impl Rpc { } } + fn features(&self) -> Result { + Ok(json!({ + "genesis_hash": self.tracker.chain().get_block_hash(0), + "hosts": { "tcp_port": self.port }, + "protocol_max": PROTOCOL_VERSION, + "protocol_min": PROTOCOL_VERSION, + "pruning": null, + "server_version": self.server_id(), + "hash_function": "sha256" + })) + } + pub fn handle_request(&self, client: &mut Client, line: &str) -> String { let error_msg_no_id = |err| error_msg(Value::Null, RpcError::Standard(err)); let response: Value = match serde_json::from_str(line) { @@ -383,6 +400,7 @@ impl Rpc { Call::BlockHeaders(args) => self.block_headers(args), Call::Donation => Ok(Value::Null), Call::EstimateFee(args) => self.estimate_fee(args), + Call::Features => self.features(), Call::HeadersSubscribe => self.headers_subscribe(client), Call::MempoolFeeHistogram => self.get_fee_histogram(), Call::PeersSubscribe => Ok(json!([])), @@ -421,6 +439,7 @@ enum Call { TransactionBroadcast((String,)), Donation, EstimateFee((u16,)), + Features, HeadersSubscribe, MempoolFeeHistogram, PeersSubscribe, @@ -451,6 +470,7 @@ impl Call { "mempool.get_fee_histogram" => Call::MempoolFeeHistogram, "server.banner" => Call::Banner, "server.donation_address" => Call::Donation, + "server.features" => Call::Features, "server.peers.subscribe" => Call::PeersSubscribe, "server.ping" => Call::Ping, "server.version" => Call::Version(convert(params)?), From 50d22425bb4f7b878da3ecde8cdd8ba14da4f7f2 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 23 Jul 2021 19:15:01 +0300 Subject: [PATCH 057/113] Drop unused rows' count --- src/db.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/db.rs b/src/db.rs index e50f954..18ea999 100644 --- a/src/db.rs +++ b/src/db.rs @@ -166,24 +166,19 @@ impl DBStore { .expect("get_tip failed") } - pub(crate) fn write(&self, batch: WriteBatch) -> usize { + pub(crate) fn write(&self, batch: WriteBatch) { let mut db_batch = rocksdb::WriteBatch::default(); - let mut total_rows_count = 0; for key in batch.funding_rows { db_batch.put_cf(self.funding_cf(), key, b""); - total_rows_count += 1; } for key in batch.spending_rows { db_batch.put_cf(self.spending_cf(), key, b""); - total_rows_count += 1; } for key in batch.txid_rows { db_batch.put_cf(self.txid_cf(), key, b""); - total_rows_count += 1; } for key in batch.header_rows { db_batch.put_cf(self.headers_cf(), key, b""); - total_rows_count += 1; } db_batch.put_cf(self.headers_cf(), TIP_KEY, batch.tip_row); @@ -192,7 +187,6 @@ impl DBStore { opts.set_sync(!bulk_import); opts.disable_wal(bulk_import); self.db.write_opt(db_batch, &opts).unwrap(); - total_rows_count } pub(crate) fn flush(&self) { From d153bfa2eb4435fbe2f70d38ece76152c0bcff66 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 27 Jul 2021 20:51:32 +0300 Subject: [PATCH 058/113] Use log verbosity level from configration Following https://github.com/romanz/electrs/issues/440#issuecomment-886094765. --- server.sh | 2 +- src/config.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/server.sh b/server.sh index 39ed75f..4ad7090 100755 --- a/server.sh +++ b/server.sh @@ -10,7 +10,7 @@ shift DB=./db2 # $HOME/tmp/electrs_db/mainnet_zstd CMD="target/release/electrs --network $NETWORK --db-dir $DB --daemon-dir $HOME/.bitcoin" -export RUST_LOG=${RUST_LOG-info} +# export RUST_LOG=${RUST_LOG-info} $CMD $* # use SIGINT to quit diff --git a/src/config.rs b/src/config.rs index 0ff4d50..17192a4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -267,6 +267,12 @@ impl Config { } }); + let level = match config.verbose { + 0 => log::LevelFilter::Info, + 1 => log::LevelFilter::Debug, + _ => log::LevelFilter::Trace, + }; + let config = Config { network: config.network, db_path: config.db_dir, @@ -286,6 +292,7 @@ impl Config { env_logger::Builder::from_default_env() .default_format() .format_timestamp_millis() + .filter_level(level) .init(); config } From ab860d931506f059a64e00593f01558c6aefe4f5 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 29 Jul 2021 14:33:44 +0300 Subject: [PATCH 059/113] Add a workaround for latest Bitcoin Core See https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/190. --- src/daemon.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index c3b008d..45d32aa 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -17,7 +17,7 @@ enum PollResult { } fn rpc_poll(client: &mut bitcoincore_rpc::Client) -> PollResult { - match client.get_blockchain_info() { + match client.call::("getblockchaininfo", &[]) { Ok(info) => { let left_blocks = info.headers - info.blocks; if info.initial_block_download || left_blocks > 0 { @@ -72,6 +72,20 @@ pub struct Daemon { rpc: bitcoincore_rpc::Client, } +// A workaround for https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/190. +#[derive(Deserialize)] +struct BlockchainInfo { + /// The current number of blocks processed in the server + pub blocks: u64, + /// The current number of headers we have validated + pub headers: u64, + /// Estimate of whether this node is in Initial Block Download mode + #[serde(rename = "initialblockdownload")] + pub initial_block_download: bool, + /// If the blocks are subject to pruning + pub pruned: bool, +} + impl Daemon { pub fn connect(config: &Config) -> Result { let rpc = rpc_connect(config)?; @@ -82,7 +96,7 @@ impl Daemon { if !network_info.network_active { bail!("electrs requires active bitcoind p2p network"); } - let blockchain_info = rpc.get_blockchain_info()?; + let blockchain_info: BlockchainInfo = rpc.call("getblockchaininfo", &[])?; if blockchain_info.pruned { bail!("electrs requires non-pruned bitcoind node"); } From 2ab5fb1da6f95fb6fb0522a70a807431008072a9 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 13 Aug 2021 20:34:14 +0300 Subject: [PATCH 060/113] Use HistoryEntry instead of {Confirmed,Mempool}Entry Should simplify JSON serialization and height computation. --- src/electrum.rs | 2 +- src/status.rs | 143 ++++++++++++++++++++++++++++++++++-------------- src/tracker.rs | 15 +---- 3 files changed, 106 insertions(+), 54 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index 3277c40..f4a2f3b 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -256,7 +256,7 @@ impl Rpc { self.tracker.get_history(&self.new_status(scripthash)?) } }; - Ok(json!(history_entries.collect::>())) + Ok(json!(history_entries)) } fn scripthash_subscribe( diff --git a/src/status.rs b/src/status.rs index d2bd588..b2f2881 100644 --- a/src/status.rs +++ b/src/status.rs @@ -4,7 +4,7 @@ use bitcoin::{ Amount, Block, BlockHash, OutPoint, SignedAmount, Transaction, Txid, }; use rayon::prelude::*; -use serde_json::{json, Value}; +use serde::ser::{Serialize, Serializer}; use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; @@ -45,44 +45,74 @@ impl TxEntry { } } -pub(crate) struct ConfirmedEntry { - txid: Txid, - height: usize, +enum Height { + Confirmed { height: usize }, + Unconfirmed { has_unconfirmed_inputs: bool }, } -impl ConfirmedEntry { - pub fn hash(&self, engine: &mut sha256::HashEngine) { +impl Height { + fn as_i64(&self) -> i64 { + match self { + Self::Confirmed { height } => i64::try_from(*height).unwrap(), + Self::Unconfirmed { + has_unconfirmed_inputs: true, + } => -1, + Self::Unconfirmed { + has_unconfirmed_inputs: false, + } => 0, + } + } +} + +impl Serialize for Height { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_i64(self.as_i64()) + } +} + +impl std::fmt::Display for Height { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_i64().fmt(f) + } +} + +#[derive(Serialize)] +pub(crate) struct HistoryEntry { + #[serde(rename = "tx_hash")] + txid: Txid, + height: Height, + #[serde( + skip_serializing_if = "Option::is_none", + with = "bitcoin::util::amount::serde::as_sat::opt" + )] + fee: Option, +} + +impl HistoryEntry { + fn hash(&self, engine: &mut sha256::HashEngine) { let s = format!("{}:{}:", self.txid, self.height); engine.input(s.as_bytes()); } - pub fn value(&self) -> Value { - json!({"tx_hash": self.txid, "height": self.height}) - } -} - -pub(crate) struct MempoolEntry { - txid: Txid, - has_unconfirmed_inputs: bool, - fee: Amount, -} - -impl MempoolEntry { - fn height(&self) -> isize { - if self.has_unconfirmed_inputs { - -1 - } else { - 0 + fn confirmed(txid: Txid, height: usize) -> Self { + Self { + txid, + height: Height::Confirmed { height }, + fee: None, } } - pub fn hash(&self, engine: &mut sha256::HashEngine) { - let s = format!("{}:{}:", self.txid, self.height()); - engine.input(s.as_bytes()); - } - - pub fn value(&self) -> Value { - json!({"tx_hash": self.txid, "height": self.height(), "fee": self.fee.as_sat()}) + fn unconfirmed(txid: Txid, has_unconfirmed_inputs: bool, fee: Amount) -> Self { + Self { + txid, + height: Height::Unconfirmed { + has_unconfirmed_inputs, + }, + fee: Some(fee), + } } } @@ -204,20 +234,25 @@ impl Status { } } - pub(crate) fn get_confirmed(&self, chain: &Chain) -> Vec { + pub(crate) fn get_history(&self, chain: &Chain, mempool: &Mempool) -> Vec { + let mut result = self.get_confirmed(chain); + result.extend(self.get_mempool(mempool)); + result + } + + fn get_confirmed(&self, chain: &Chain) -> Vec { self.confirmed_entries(chain) .collect::>() .into_iter() .flat_map(|(height, entries)| { - entries.iter().map(move |e| ConfirmedEntry { - txid: e.txid, - height, - }) + entries + .iter() + .map(move |e| HistoryEntry::confirmed(e.txid, height)) }) .collect() } - pub(crate) fn get_mempool(&self, mempool: &Mempool) -> Vec { + fn get_mempool(&self, mempool: &Mempool) -> Vec { let mut entries = self .mempool .iter() @@ -226,11 +261,7 @@ impl Status { entries.sort_by_key(|e| (e.has_unconfirmed_inputs, e.txid)); entries .into_iter() - .map(|e| MempoolEntry { - txid: e.txid, - has_unconfirmed_inputs: e.has_unconfirmed_inputs, - fee: e.fee, - }) + .map(|e| HistoryEntry::unconfirmed(e.txid, e.has_unconfirmed_inputs, e.fee)) .collect() } @@ -417,3 +448,33 @@ fn filter_inputs(tx: &Transaction, outpoints: &HashSet) -> Vec impl Iterator { - let confirmed = status - .get_confirmed(self.index.chain()) - .into_iter() - .map(|entry| entry.value()); - let mempool = status - .get_mempool(&self.mempool) - .into_iter() - .map(|entry| entry.value()); - confirmed.chain(mempool) + pub(crate) fn get_history(&self, status: &Status) -> Vec { + status.get_history(self.index.chain(), &self.mempool) } pub fn sync(&mut self, daemon: &Daemon) -> Result<()> { From 1c198de0be8be6ae58b7ce204cafbeda835d20cd Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 13 Aug 2021 20:34:28 +0300 Subject: [PATCH 061/113] Add a few more tests to types.rs --- src/types.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index 7151618..1f21126 100644 --- a/src/types.rs +++ b/src/types.rs @@ -235,7 +235,7 @@ impl HeaderRow { #[cfg(test)] mod tests { - use crate::types::{spending_prefix, ScriptHash, ScriptHashRow, SpendingPrefix}; + use crate::types::{spending_prefix, ScriptHash, ScriptHashRow, SpendingPrefix, TxidRow}; use bitcoin::{hashes::hex::ToHex, Address, OutPoint, Txid}; use serde_json::{from_str, json}; @@ -270,6 +270,33 @@ mod tests { ); } + #[test] + fn test_txid1_prefix() { + // duplicate txids from BIP-30 + let hex = "d5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599"; + let txid = Txid::from_str(hex).unwrap(); + + let row1 = TxidRow::new(txid, 91812); + let row2 = TxidRow::new(txid, 91842); + + assert_eq!(row1.to_db_row().to_hex(), "9985d82954e10f22a4660100"); + assert_eq!(row2.to_db_row().to_hex(), "9985d82954e10f22c2660100"); + } + + #[test] + fn test_txid2_prefix() { + // duplicate txids from BIP-30 + let hex = "e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468"; + let txid = Txid::from_str(hex).unwrap(); + + let row1 = TxidRow::new(txid, 91722); + let row2 = TxidRow::new(txid, 91880); + + // low-endian encoding => rows should be sorted according to block height + assert_eq!(row1.to_db_row().to_hex(), "68b45f58b674e94e4a660100"); + assert_eq!(row2.to_db_row().to_hex(), "68b45f58b674e94ee8660100"); + } + #[test] fn test_spending_prefix() { let hex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; From 27e7541644ca8fc2454d0d52a0c9c814f2599397 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 13 Aug 2021 20:34:32 +0300 Subject: [PATCH 062/113] Rename scripthash status --- src/bin/query.rs | 8 ++++---- src/electrum.rs | 20 ++++++++++---------- src/lib.rs | 2 +- src/status.rs | 4 ++-- src/tracker.rs | 10 +++++----- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/bin/query.rs b/src/bin/query.rs index 78ac02b..a57e700 100644 --- a/src/bin/query.rs +++ b/src/bin/query.rs @@ -7,7 +7,7 @@ use bitcoin::{Address, Amount}; use std::collections::BTreeMap; use std::str::FromStr; -use electrs::{Balance, Cache, Config, Daemon, ScriptHash, Status, Tracker}; +use electrs::{Balance, Cache, Config, Daemon, ScriptHash, ScriptHashStatus, Tracker}; fn main() -> Result<()> { let config = Config::from_args(); @@ -19,9 +19,9 @@ fn main() -> Result<()> { let cache = Cache::default(); let daemon = Daemon::connect(&config)?; let mut tracker = Tracker::new(&config)?; - let mut map: BTreeMap = addresses + let mut map: BTreeMap = addresses .map(|addr| { - let status = Status::new(ScriptHash::new(&addr.script_pubkey())); + let status = ScriptHashStatus::new(ScriptHash::new(&addr.script_pubkey())); (addr, status) }) .collect(); @@ -30,7 +30,7 @@ fn main() -> Result<()> { tracker.sync(&daemon)?; let mut total = Amount::ZERO; for (addr, status) in map.iter_mut() { - tracker.update_status(status, &daemon, &cache)?; + tracker.update_scripthash_status(status, &daemon, &cache)?; let balance = tracker.get_balance(status, &cache); if balance != Balance::default() { info!("{} has {}", addr, balance.confirmed()); diff --git a/src/electrum.rs b/src/electrum.rs index f4a2f3b..754a0c0 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -17,7 +17,7 @@ use crate::{ daemon::{self, extract_bitcoind_error, Daemon}, merkle::Proof, metrics::Histogram, - status::Status, + status::ScriptHashStatus, tracker::Tracker, types::ScriptHash, }; @@ -31,7 +31,7 @@ const UNKNOWN_FEE: isize = -1; // (allowed by Electrum protocol) #[derive(Default)] pub struct Client { tip: Option, - status: HashMap, + scripthashes: HashMap, } #[derive(Deserialize)] @@ -142,12 +142,12 @@ impl Rpc { pub fn update_client(&self, client: &mut Client) -> Result> { let chain = self.tracker.chain(); let mut notifications = client - .status + .scripthashes .par_iter_mut() .filter_map(|(scripthash, status)| -> Option> { match self .tracker - .update_status(status, &self.daemon, &self.cache) + .update_scripthash_status(status, &self.daemon, &self.cache) { Ok(true) => Some(Ok(notification( "blockchain.scripthash.subscribe", @@ -225,7 +225,7 @@ impl Rpc { client: &Client, (scripthash,): (ScriptHash,), ) -> Result { - let balance = match client.status.get(&scripthash) { + let balance = match client.scripthashes.get(&scripthash) { Some(status) => self.tracker.get_balance(status, &self.cache), None => { warn!( @@ -246,7 +246,7 @@ impl Rpc { client: &Client, (scripthash,): (ScriptHash,), ) -> Result { - let history_entries = match client.status.get(&scripthash) { + let history_entries = match client.scripthashes.get(&scripthash) { Some(status) => self.tracker.get_history(status), None => { warn!( @@ -266,14 +266,14 @@ impl Rpc { ) -> Result { let status = self.new_status(scripthash)?; let statushash = status.statushash(); - client.status.insert(scripthash, status); // skip if already exists + client.scripthashes.insert(scripthash, status); // skip if already exists Ok(json!(statushash)) } - fn new_status(&self, scripthash: ScriptHash) -> Result { - let mut status = Status::new(scripthash); + fn new_status(&self, scripthash: ScriptHash) -> Result { + let mut status = ScriptHashStatus::new(scripthash); self.tracker - .update_status(&mut status, &self.daemon, &self.cache)?; + .update_scripthash_status(&mut status, &self.daemon, &self.cache)?; Ok(status) } diff --git a/src/lib.rs b/src/lib.rs index 7f3ebe6..4d8be19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub use { config::Config, daemon::Daemon, electrum::{Client, Rpc}, - status::{Balance, Status}, + status::{Balance, ScriptHashStatus}, tracker::Tracker, types::ScriptHash, }; diff --git a/src/status.rs b/src/status.rs index b2f2881..46c2328 100644 --- a/src/status.rs +++ b/src/status.rs @@ -117,7 +117,7 @@ impl HistoryEntry { } /// ScriptHash subscription status -pub struct Status { +pub struct ScriptHashStatus { scripthash: ScriptHash, tip: BlockHash, statushash: Option, @@ -171,7 +171,7 @@ fn make_outpoints<'a>(txid: &'a Txid, outputs: &'a [u32]) -> impl Iterator Self { Self { scripthash, diff --git a/src/tracker.rs b/src/tracker.rs index 22a5a66..cebd428 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -13,7 +13,7 @@ use crate::{ index::Index, mempool::{Histogram, Mempool}, metrics::Metrics, - status::{Balance, HistoryEntry, Status}, + status::{Balance, HistoryEntry, ScriptHashStatus}, }; /// Electrum protocol subscriptions' tracker @@ -51,7 +51,7 @@ impl Tracker { &self.metrics } - pub(crate) fn get_history(&self, status: &Status) -> Vec { + pub(crate) fn get_history(&self, status: &ScriptHashStatus) -> Vec { status.get_history(self.index.chain(), &self.mempool) } @@ -64,9 +64,9 @@ impl Tracker { Ok(()) } - pub fn update_status( + pub fn update_scripthash_status( &self, - status: &mut Status, + status: &mut ScriptHashStatus, daemon: &Daemon, cache: &Cache, ) -> Result { @@ -75,7 +75,7 @@ impl Tracker { Ok(prev_statushash != status.statushash()) } - pub fn get_balance(&self, status: &Status, cache: &Cache) -> Balance { + pub fn get_balance(&self, status: &ScriptHashStatus, cache: &Cache) -> Balance { let get_amount_fn = |outpoint: OutPoint| { cache .get_tx(&outpoint.txid, |tx| { From 4dd66f67137bc49c0d5c152212b3e5e321f6e5c7 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 15 Aug 2021 21:09:52 +0300 Subject: [PATCH 063/113] Bump bitcoind version in Dockerfile --- Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1a987f2..c222ae3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,10 +20,11 @@ FROM updated as bitcoin-build # Download RUN apt-get install -qqy wget WORKDIR /build/bitcoin -RUN wget -q https://bitcoincore.org/bin/bitcoin-core-0.21.0/bitcoin-0.21.0-x86_64-linux-gnu.tar.gz -RUN tar xvf bitcoin-0.21.0-x86_64-linux-gnu.tar.gz -RUN mv -v bitcoin-0.21.0/bin/bitcoind . -RUN mv -v bitcoin-0.21.0/bin/bitcoin-cli . +ARG BITCOIND_VERSION=0.21.1 +RUN wget -q https://bitcoincore.org/bin/bitcoin-core-$BITCOIND_VERSION/bitcoin-$BITCOIND_VERSION-x86_64-linux-gnu.tar.gz +RUN tar xvf bitcoin-$BITCOIND_VERSION-x86_64-linux-gnu.tar.gz +RUN mv -v bitcoin-$BITCOIND_VERSION/bin/bitcoind . +RUN mv -v bitcoin-$BITCOIND_VERSION/bin/bitcoin-cli . FROM updated as result # Copy the binaries From aa6c5b15788a9a9e0467ef6960722a894ae687bb Mon Sep 17 00:00:00 2001 From: Pantamis Date: Mon, 16 Aug 2021 12:58:03 +0200 Subject: [PATCH 064/113] Use the same log verbosity levels as master Current verbosity level of master: https://docs.rs/stderrlog/0.5.1/src/stderrlog/lib.rs.html#377 --- src/config.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 17192a4..0242d3a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -268,9 +268,11 @@ impl Config { }); let level = match config.verbose { - 0 => log::LevelFilter::Info, - 1 => log::LevelFilter::Debug, - _ => log::LevelFilter::Trace, + 0 => LevelFilter::Error, + 1 => LevelFilter::Warn, + 2 => LevelFilter::Info, + 3 => LevelFilter::Debug, + _ => LevelFilter::Trace, }; let config = Config { From fa531808464ac525b6890e36118897ef20b666d2 Mon Sep 17 00:00:00 2001 From: Pantamis Date: Mon, 16 Aug 2021 13:08:49 +0200 Subject: [PATCH 065/113] Small fix --- src/config.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0242d3a..c5faa42 100644 --- a/src/config.rs +++ b/src/config.rs @@ -268,11 +268,11 @@ impl Config { }); let level = match config.verbose { - 0 => LevelFilter::Error, - 1 => LevelFilter::Warn, - 2 => LevelFilter::Info, - 3 => LevelFilter::Debug, - _ => LevelFilter::Trace, + 0 => log::LevelFilter::Error, + 1 => log::LevelFilter::Warn, + 2 => log::LevelFilter::Info, + 3 => log::LevelFilter::Debug, + _ => log::LevelFilter::Trace, }; let config = Config { From 95134a5157346cc14b0771f84399d44428b6fa96 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 18 Aug 2021 14:12:30 +0300 Subject: [PATCH 066/113] Drop unused command-line parameters --- internal/config_specification.toml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/internal/config_specification.toml b/internal/config_specification.toml index 15ba95d..0e42758 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -29,11 +29,6 @@ type = "std::path::PathBuf" doc = "Data directory of Bitcoind (default: ~/.bitcoin/)" default = "crate::config::default_daemon_dir()" -[[param]] -name = "blocks_dir" -type = "std::path::PathBuf" -doc = "Analogous to bitcoind's -blocksdir option, this specifies the directory containing the raw blocks files (blk*.dat)" - [[param]] name = "auth" type = "String" @@ -74,10 +69,6 @@ name = "monitoring_addr" type = "crate::config::ResolvAddr" doc = "Prometheus monitoring 'addr:port' to listen on (default: 127.0.0.1:4224 for mainnet, 127.0.0.1:14224 for testnet, 127.0.0.1:24224 for regtest and 127.0.0.1:34224 for regtest)" -[[switch]] -name = "jsonrpc_import" -doc = "Use JSONRPC instead of directly importing blk*.dat files. Useful for remote full node or low memory system" - [[param]] name = "wait_duration_secs" type = "u64" From 321dc23767e20186db5e316b0e5da0e954e03e39 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 17 Aug 2021 16:27:46 +0300 Subject: [PATCH 067/113] Fix crates.io badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b14fc60..2cef7d1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![workflows](https://github.com/romanz/electrs/workflows/Rust/badge.svg)](https://github.com/romanz/electrs/actions) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) -[![crates.io](http://meritbadge.herokuapp.com/electrs)](https://crates.io/crates/electrs) +[![crates.io](https://img.shields.io/crates/v/electrs.svg)](https://crates.io/crates/electrs) [![gitter.im](https://badges.gitter.im/romanz/electrs.svg)](https://gitter.im/romanz/electrs) An efficient re-implementation of Electrum Server, inspired by [ElectrumX](https://github.com/kyuupichan/electrumx), [Electrum Personal Server](https://github.com/chris-belcher/electrum-personal-server) and [bitcoincore-indexd](https://github.com/jonasschnelli/bitcoincore-indexd). From 31765e3e49ea93c97721675e2c06e2a9efa76812 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 17 Aug 2021 14:15:16 +0300 Subject: [PATCH 068/113] Limit index lookups to prevent DoS for "popular" addresses --- internal/config_specification.toml | 2 +- src/config.rs | 6 ++++++ src/index.rs | 21 ++++++++++++++++++++- src/status.rs | 2 +- src/tracker.rs | 3 ++- tests/run.sh | 7 ++++++- 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/internal/config_specification.toml b/internal/config_specification.toml index 0e42758..e5d0168 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -86,7 +86,7 @@ name = "ignore_mempool" doc = "Don't sync mempool - queries will show only confirmed transactions." [[param]] -name = "txid_limit" +name = "index_lookup_limit" type = "usize" doc = "Number of transactions to lookup before returning an error, to prevent 'too popular' addresses from causing the RPC server to get stuck (0 - disable the limit)" default = "100" diff --git a/src/config.rs b/src/config.rs index c5faa42..f557e5c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -130,6 +130,7 @@ pub struct Config { pub monitoring_addr: SocketAddr, pub wait_duration: Duration, pub index_batch_size: usize, + pub index_lookup_limit: Option, pub ignore_mempool: bool, pub server_banner: String, pub args: Vec, @@ -275,6 +276,10 @@ impl Config { _ => log::LevelFilter::Trace, }; + let index_lookup_limit = match config.index_lookup_limit { + 0 => None, + _ => Some(config.index_lookup_limit), + }; let config = Config { network: config.network, db_path: config.db_dir, @@ -286,6 +291,7 @@ impl Config { monitoring_addr, wait_duration: Duration::from_secs(config.wait_duration_secs), index_batch_size: config.index_batch_size, + index_lookup_limit, ignore_mempool: config.ignore_mempool, server_banner: config.server_banner, args: args.map(|a| a.into_string().unwrap()).collect(), diff --git a/src/index.rs b/src/index.rs index ade0777..ecfabb3 100644 --- a/src/index.rs +++ b/src/index.rs @@ -85,12 +85,18 @@ impl IndexResult { /// Confirmed transactions' address index pub struct Index { store: DBStore, + lookup_limit: Option, chain: Chain, stats: Stats, } impl Index { - pub(crate) fn load(store: DBStore, mut chain: Chain, metrics: &Metrics) -> Result { + pub(crate) fn load( + store: DBStore, + mut chain: Chain, + metrics: &Metrics, + lookup_limit: Option, + ) -> Result { if let Some(row) = store.get_tip() { let tip = deserialize(&row).expect("invalid tip"); let headers = store @@ -103,6 +109,7 @@ impl Index { Ok(Index { store, + lookup_limit, chain, stats: Stats::new(metrics), }) @@ -112,6 +119,18 @@ impl Index { &self.chain } + pub(crate) fn limit_result(&self, entries: impl Iterator) -> Result> { + let mut entries = entries.fuse(); + let result: Vec = match self.lookup_limit { + Some(lookup_limit) => entries.by_ref().take(lookup_limit).collect(), + None => entries.by_ref().collect(), + }; + if entries.next().is_some() { + bail!(">{} index entries, query may take too long", result.len()) + } + Ok(result) + } + pub(crate) fn filter_by_txid(&self, txid: Txid) -> impl Iterator + '_ { self.store .iter_txid(TxidRow::scan_prefix(txid)) diff --git a/src/status.rs b/src/status.rs index 46c2328..add5c85 100644 --- a/src/status.rs +++ b/src/status.rs @@ -288,7 +288,7 @@ impl ScriptHashStatus { type PosTxid = (u32, Txid); let mut result = HashMap::>::new(); - let funding_blockhashes = index.filter_by_funding(self.scripthash); + let funding_blockhashes = index.limit_result(index.filter_by_funding(self.scripthash))?; self.for_new_blocks(funding_blockhashes, daemon, |blockhash, block| { let txids: Vec = block.txdata.iter().map(|tx| tx.txid()).collect(); for (pos, (tx, txid)) in block.txdata.into_iter().zip(txids.iter()).enumerate() { diff --git a/src/tracker.rs b/src/tracker.rs index cebd428..8337bcd 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -31,7 +31,8 @@ impl Tracker { let store = DBStore::open(Path::new(&config.db_path))?; let chain = Chain::new(config.network); Ok(Self { - index: Index::load(store, chain, &metrics).context("failed to open index")?, + index: Index::load(store, chain, &metrics, config.index_lookup_limit) + .context("failed to open index")?, mempool: Mempool::new(), metrics, index_batch_size: config.index_batch_size, diff --git a/tests/run.sh b/tests/run.sh index e3f807d..fb99584 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -40,7 +40,12 @@ echo `$BTC getblockchaininfo | jq -r '"Generated \(.blocks) regtest blocks (\(.s TIP=`$BTC getbestblockhash` export RUST_LOG=electrs=debug -electrs --db-dir=data/electrs --daemon-dir=data/bitcoin --network=regtest 2> data/electrs/regtest-debug.log & +electrs \ + --index-lookup-limit 200 \ + --db-dir=data/electrs \ + --daemon-dir=data/bitcoin \ + --network=regtest \ + 2> data/electrs/regtest-debug.log & ELECTRS_PID=$! tail_log data/electrs/regtest-debug.log | grep -m1 "serving Electrum RPC" From 33e2c729239f12d389d6dacba66e67076031dd5b Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 18 Aug 2021 18:06:48 +0300 Subject: [PATCH 069/113] Fixup a clippy nit --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index f557e5c..202876c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -255,7 +255,7 @@ impl Config { (None, None) => Auth::CookieFile(daemon_dir.join(".cookie")), (None, Some(cookie_file)) => Auth::CookieFile(cookie_file), (Some(auth), None) => { - let parts: Vec<&str> = auth.splitn(2, ":").collect(); + let parts: Vec<&str> = auth.splitn(2, ':').collect(); if parts.len() != 2 { eprintln!("Error: auth cookie doesn't contain colon"); std::process::exit(1); From b43173881db933ef8696ca13d37fba58b6b08755 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 8 Jul 2021 14:37:34 +0300 Subject: [PATCH 070/113] Disable 'clippy::identity_conversion' on generated configuration handling code Otherwise, clippy fails with: warning: identical conversion --> /media/roman/3TB/roman/Code/electrs/target/debug/build/electrs-56b1e395b2bcf88c/out/configure_me_config.rs:339:25 | 339 | db_dir: db_dir.into(), | ^^^^^^^^^^^^^ help: consider removing `.into()`: `db_dir` | = note: `#[warn(clippy::identity_conversion)]` on by default = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion warning: identical conversion --> /media/roman/3TB/roman/Code/electrs/target/debug/build/electrs-56b1e395b2bcf88c/out/configure_me_config.rs:340:29 | 340 | daemon_dir: daemon_dir.into(), | ^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `daemon_dir` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion warning: identical conversion --> /media/roman/3TB/roman/Code/electrs/target/debug/build/electrs-56b1e395b2bcf88c/out/configure_me_config.rs:349:37 | 349 | wait_duration_secs: wait_duration_secs.into(), | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `wait_duration_secs` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion warning: identical conversion --> /media/roman/3TB/roman/Code/electrs/target/debug/build/electrs-56b1e395b2bcf88c/out/configure_me_config.rs:350:35 | 350 | index_batch_size: index_batch_size.into(), | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `index_batch_size` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion warning: identical conversion --> /media/roman/3TB/roman/Code/electrs/target/debug/build/electrs-56b1e395b2bcf88c/out/configure_me_config.rs:351:29 | 351 | txid_limit: txid_limit.into(), | ^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `txid_limit` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion warning: identical conversion --> /media/roman/3TB/roman/Code/electrs/target/debug/build/electrs-56b1e395b2bcf88c/out/configure_me_config.rs:352:32 | 352 | server_banner: server_banner.into(), | ^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `server_banner` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion --- src/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.rs b/src/config.rs index 202876c..8d5bbb5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ const DEFAULT_SERVER_ADDRESS: [u8; 4] = [127, 0, 0, 1]; // by default, serve on mod internal { #![allow(unused)] + #![allow(clippy::identity_conversion)] include!(concat!(env!("OUT_DIR"), "/configure_me_config.rs")); } From 79f38d8852c6e0d36eb5efac042aba684f1464d0 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 21 Aug 2021 19:11:45 +0300 Subject: [PATCH 071/113] Add clippy check to Dockerfile build --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c222ae3..4ef3ec0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,13 @@ FROM rust:1.41.1-slim as electrs-build RUN apt-get update RUN apt-get install -qq -y clang cmake -RUN rustup component add rustfmt +RUN rustup component add rustfmt clippy # Build, test and install electrs WORKDIR /build/electrs COPY . . RUN cargo fmt -- --check +RUN cargo clippy RUN cargo build --locked --release --all RUN cargo test --locked --release --all RUN cargo install --locked --path . From c1c1b263334620b60ec87d21d8fe3877b7359203 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 22 Aug 2021 17:03:47 +0300 Subject: [PATCH 072/113] Use INFO logging level by default from server.sh --- server.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server.sh b/server.sh index 4ad7090..fd4ddf8 100755 --- a/server.sh +++ b/server.sh @@ -8,9 +8,8 @@ cargo build --all --release NETWORK=$1 shift -DB=./db2 # $HOME/tmp/electrs_db/mainnet_zstd -CMD="target/release/electrs --network $NETWORK --db-dir $DB --daemon-dir $HOME/.bitcoin" -# export RUST_LOG=${RUST_LOG-info} -$CMD $* +DB=./db2 +export RUST_LOG=${RUST_LOG-INFO} +target/release/electrs --network $NETWORK --db-dir $DB --daemon-dir $HOME/.bitcoin $* # use SIGINT to quit From 9796a4433c01908d998303dcd7401673dbadfde5 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 22 Aug 2021 21:53:51 +0300 Subject: [PATCH 073/113] Log RocksDB statistics after each flush --- src/db.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/db.rs b/src/db.rs index 18ea999..386dbac 100644 --- a/src/db.rs +++ b/src/db.rs @@ -206,6 +206,16 @@ impl DBStore { info!("finished full compaction"); self.start_compactions(); } + if log_enabled!(log::Level::Trace) { + for property in &["rocksdb.dbstats"] { + let stats = self + .db + .property_value(property) + .expect("failed to get property") + .expect("missing property"); + trace!("{}: {}", property, stats); + } + } } fn start_compactions(&self) { From 7baacab5604eca99c5d796ea00c28e30f9a66652 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 22 Aug 2021 22:33:09 +0300 Subject: [PATCH 074/113] Fixup default logging --- server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.sh b/server.sh index fd4ddf8..b0276a7 100755 --- a/server.sh +++ b/server.sh @@ -9,7 +9,7 @@ NETWORK=$1 shift DB=./db2 -export RUST_LOG=${RUST_LOG-INFO} +export RUST_LOG=${RUST_LOG-electrs=INFO} target/release/electrs --network $NETWORK --db-dir $DB --daemon-dir $HOME/.bitcoin $* # use SIGINT to quit From c7eb0ba56a9bb023cc3d132d0cff98f4bc0d3e36 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 23 Aug 2021 11:56:51 +0300 Subject: [PATCH 075/113] Support regtest in contrib/history.py --- contrib/history.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contrib/history.py b/contrib/history.py index be92c64..7e1b6f2 100755 --- a/contrib/history.py +++ b/contrib/history.py @@ -30,16 +30,21 @@ def show_rows(rows, field_names): def main(): parser = argparse.ArgumentParser() - parser.add_argument('--testnet', action='store_true') + parser.add_argument('--network', default='mainnet') parser.add_argument('address', nargs='+') args = parser.parse_args() - if args.testnet: + if args.network == 'regtest': + port = 60401 + from pycoin.symbols.xrt import network + elif args.network == 'testnet': port = 60001 from pycoin.symbols.xtn import network - else: + elif args.network == 'mainnet': port = 50001 from pycoin.symbols.btc import network + else: + raise ValueError(f"unknown network: {args.network}") hostport = ('localhost', port) log.info('connecting to {}:{}', *hostport) From ffab4f4efa74efa3b1a175d49840e1e09f95896c Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Tue, 24 Aug 2021 23:30:45 +0300 Subject: [PATCH 076/113] Fix incorrect JSON response for 'blockchain.transaction.get' RPC Daemon::get_transaction_hex should return a string, not a dictionary. --- src/daemon.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 45d32aa..d7a5ef8 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,6 +1,8 @@ use anyhow::{Context, Result}; -use bitcoin::{Amount, Block, BlockHash, Transaction, Txid}; +use bitcoin::{ + consensus::serialize, hashes::hex::ToHex, Amount, Block, BlockHash, Transaction, Txid, +}; use bitcoincore_rpc::{self, json, RpcApi}; use parking_lot::Mutex; use serde_json::{json, Value}; @@ -144,10 +146,9 @@ impl Daemon { &self, txid: &Txid, blockhash: Option, - ) -> Result { - self.rpc - .get_raw_transaction_info(txid, blockhash.as_ref()) - .context("failed to get transaction info") + ) -> Result { + let tx = self.get_transaction(txid, blockhash)?; + Ok(json!(serialize(&tx).to_hex())) } pub(crate) fn get_transaction( From 4b24e649d614f632a34c01aad05feeab1dc9a9db Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 26 Aug 2021 23:16:00 +0300 Subject: [PATCH 077/113] Move local-electrum.bash to contrib/ --- {scripts => contrib}/local-electrum.bash | 0 doc/usage.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {scripts => contrib}/local-electrum.bash (100%) diff --git a/scripts/local-electrum.bash b/contrib/local-electrum.bash similarity index 100% rename from scripts/local-electrum.bash rename to contrib/local-electrum.bash diff --git a/doc/usage.md b/doc/usage.md index 98a22bb..eaeda8d 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -306,7 +306,7 @@ Read below otherwise. There's a prepared script for launching `electrum` in such way to connect only to the local `electrs` instance to protect your privacy. ```bash -$ ./scripts/local-electrum.bash +$ ./contrib/local-electrum.bash + ADDR=127.0.0.1 + PORT=50001 + PROTOCOL=t From 100864183825ff29f70ee2fdfdae8bbb241dd3cf Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 27 Aug 2021 09:28:31 +0300 Subject: [PATCH 078/113] Increase default `index_lookup_limit` to 200 Otherwise it fails when mining >100 regtest blocks to the same address. --- internal/config_specification.toml | 2 +- tests/run.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/config_specification.toml b/internal/config_specification.toml index e5d0168..e3a73f3 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -89,7 +89,7 @@ doc = "Don't sync mempool - queries will show only confirmed transactions." name = "index_lookup_limit" type = "usize" doc = "Number of transactions to lookup before returning an error, to prevent 'too popular' addresses from causing the RPC server to get stuck (0 - disable the limit)" -default = "100" +default = "200" [[param]] name = "server_banner" diff --git a/tests/run.sh b/tests/run.sh index fb99584..b8572dc 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -41,7 +41,6 @@ TIP=`$BTC getbestblockhash` export RUST_LOG=electrs=debug electrs \ - --index-lookup-limit 200 \ --db-dir=data/electrs \ --daemon-dir=data/bitcoin \ --network=regtest \ From 6a7cd19946fda24b1ca8a279715642b5ee502535 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 27 Aug 2021 09:29:26 +0300 Subject: [PATCH 079/113] Reuse scripthash subscription --- src/electrum.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index 754a0c0..dd9af35 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -8,7 +8,7 @@ use rayon::prelude::*; use serde_derive::Deserialize; use serde_json::{self, json, Value}; -use std::collections::HashMap; +use std::collections::{hash_map::Entry, HashMap}; use std::iter::FromIterator; use crate::{ @@ -264,10 +264,11 @@ impl Rpc { client: &mut Client, (scripthash,): (ScriptHash,), ) -> Result { - let status = self.new_status(scripthash)?; - let statushash = status.statushash(); - client.scripthashes.insert(scripthash, status); // skip if already exists - Ok(json!(statushash)) + let result = match client.scripthashes.entry(scripthash) { + Entry::Occupied(e) => e.get().statushash(), + Entry::Vacant(e) => e.insert(self.new_status(scripthash)?).statushash(), + }; + Ok(json!(result)) } fn new_status(&self, scripthash: ScriptHash) -> Result { From e8fb55b8887185c83ac33fd4ba6ab27e6b669bed Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 27 Aug 2021 14:47:39 +0300 Subject: [PATCH 080/113] Use './db' as default DB directory in server.sh --- server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.sh b/server.sh index b0276a7..db69ff6 100755 --- a/server.sh +++ b/server.sh @@ -8,7 +8,7 @@ cargo build --all --release NETWORK=$1 shift -DB=./db2 +DB=./db export RUST_LOG=${RUST_LOG-electrs=INFO} target/release/electrs --network $NETWORK --db-dir $DB --daemon-dir $HOME/.bitcoin $* From 0516aead2a3bda463cdaf7018452d3b89631d6a1 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 27 Aug 2021 19:35:38 +0300 Subject: [PATCH 081/113] Bump bitcoin dependency to 0.27 A bit hacky solution until https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/196 is merged. --- Cargo.lock | 44 ++++++++++++++++++++++---------------------- Cargo.toml | 8 ++++++-- src/daemon.rs | 2 +- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f19932..57b8cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,10 +55,19 @@ dependencies = [ ] [[package]] -name = "bech32" -version = "0.7.3" +name = "base64-compat" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + +[[package]] +name = "bech32" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" [[package]] name = "bindgen" @@ -82,9 +91,9 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.26.2" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6742ec672d3f12506f4ac5c0d853926ff1f94e675f60ffd3224039972bf663f1" +checksum = "8a427b27dae305157520d86673f2393b3eb08d880609abfcffc6e3c3c820e764" dependencies = [ "bech32", "bitcoin_hashes", @@ -94,9 +103,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.9.7" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1" +checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" dependencies = [ "serde", ] @@ -104,8 +113,7 @@ dependencies = [ [[package]] name = "bitcoincore-rpc" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d708433972bf78bd5f909d1d288f9ac1cceeab1460edb954e962f83e1f440a3" +source = "git+https://github.com/romanz/rust-bitcoincore-rpc?rev=06ac9fa3e834413f7afeaed322cf8098d876e4a0#06ac9fa3e834413f7afeaed322cf8098d876e4a0" dependencies = [ "bitcoincore-rpc-json", "jsonrpc", @@ -117,11 +125,9 @@ dependencies = [ [[package]] name = "bitcoincore-rpc-json" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977e55a945ab1e3c446dea93267876703c15e07c7d6eeb1dfa1766b3190c560f" +source = "git+https://github.com/romanz/rust-bitcoincore-rpc?rev=06ac9fa3e834413f7afeaed322cf8098d876e4a0#06ac9fa3e834413f7afeaed322cf8098d876e4a0" dependencies = [ "bitcoin", - "hex 0.3.2", "serde", "serde_json", ] @@ -400,12 +406,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hex" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" - [[package]] name = "hex" version = "0.4.3" @@ -483,11 +483,11 @@ dependencies = [ [[package]] name = "jsonrpc" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436f3455a8a4e9c7b14de9f1206198ee5d0bdc2db1b560339d2141093d7dd389" +checksum = "ad24d69a8a0698db8ffb9048e937e8ae3ee3bc45772a5d7b6979b1d2d5b6a9f7" dependencies = [ - "hyper", + "base64-compat", "serde", "serde_derive", "serde_json", @@ -695,7 +695,7 @@ dependencies = [ "bitflags", "byteorder", "flate2", - "hex 0.4.3", + "hex", "lazy_static", "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 4a85932..678de52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,7 @@ spec = "internal/config_specification.toml" [dependencies] anyhow = "1.0" -bitcoin = { version = "0.26", features = ["use-serde", "rand"] } -bitcoincore-rpc = "0.13" +bitcoin = { version = "0.27", features = ["use-serde", "rand"] } configure_me = "0.4" crossbeam-channel = "0.5" dirs-next = "2.0" @@ -37,6 +36,11 @@ serde_derive = "1.0" serde_json = "1.0" signal-hook = "0.3" +[dependencies.bitcoincore-rpc] +# use bitcoin 0.27 (until https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/196 is merged) +git = "https://github.com/romanz/rust-bitcoincore-rpc" +rev = "06ac9fa3e834413f7afeaed322cf8098d876e4a0" + [dependencies.rocksdb] # support building with Rust 1.41.1 and workaround https://github.com/romanz/electrs/issues/403 git = "https://github.com/romanz/rust-rocksdb" diff --git a/src/daemon.rs b/src/daemon.rs index d7a5ef8..3fd1d42 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -56,7 +56,7 @@ pub(crate) fn rpc_connect(config: &Config) -> Result { bail!("{:?} is missing - is bitcoind running?", path); } } - let mut client = bitcoincore_rpc::Client::new(rpc_url, auth) + let mut client = bitcoincore_rpc::Client::new(&rpc_url, auth) .with_context(|| format!("failed to connect to RPC: {}", config.daemon_rpc_addr))?; loop { From 7409142edf6a047b2377bdcb85d4c7018e019d92 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 27 Aug 2021 20:42:54 +0300 Subject: [PATCH 082/113] Bump crossbeam-deque to 0.8.1 Using `cargo update -p crossbeam-deque --precise "0.8.1"` --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57b8cb2..96662d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,9 +254,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", From 64230332ccef4e5b25829fac965156bee4ac5ce7 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 27 Aug 2021 20:53:01 +0300 Subject: [PATCH 083/113] Refactor thread spawning into a separate module --- src/lib.rs | 1 + src/signals.rs | 8 +++++--- src/thread.rs | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 src/thread.rs diff --git a/src/lib.rs b/src/lib.rs index 4d8be19..95935f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ mod p2p; pub mod server; mod signals; mod status; +mod thread; mod tracker; mod types; diff --git a/src/signals.rs b/src/signals.rs index 4bcc43c..810cce6 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,8 +1,9 @@ +use anyhow::Context; use crossbeam_channel::{unbounded, Receiver}; use signal_hook::consts::signal::*; use signal_hook::iterator::Signals; -use std::thread; +use crate::thread::spawn; pub(crate) enum Signal { Exit, @@ -16,15 +17,16 @@ pub(crate) fn register() -> Receiver { ]; let (tx, rx) = unbounded(); let mut signals = Signals::new(&ids).expect("failed to register signal hook"); - thread::spawn(move || { + spawn("signal", move || { for id in &mut signals { info!("notified via SIG{}", id); let signal = match id { SIGUSR1 => Signal::Trigger, _ => Signal::Exit, }; - tx.send(signal).expect("failed to send signal"); + tx.send(signal).context("failed to send signal")?; } + Ok(()) }); rx } diff --git a/src/thread.rs b/src/thread.rs new file mode 100644 index 0000000..caef419 --- /dev/null +++ b/src/thread.rs @@ -0,0 +1,15 @@ +use anyhow::Result; + +pub(crate) fn spawn(name: &'static str, f: F) -> std::thread::JoinHandle<()> +where + F: 'static + Send + FnOnce() -> Result<()>, +{ + std::thread::Builder::new() + .name(name.to_owned()) + .spawn(move || { + if let Err(e) = f() { + warn!("{} thread failed: {}", name, e); + } + }) + .expect("failed to spawn a thread") +} From f9346d2a1d3ee2e2f1f3c4e8c5aa0e9ec049f359 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 27 Aug 2021 20:40:54 +0300 Subject: [PATCH 084/113] Drop hyper dependency https://rustsec.org/advisories/RUSTSEC-2021-0078 Use tiny_http for Prometheus instead. --- Cargo.lock | 181 ++++++++++++++++++++----------------------------- Cargo.toml | 4 +- src/metrics.rs | 57 ++++++---------- src/server.rs | 16 +---- 4 files changed, 96 insertions(+), 162 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96662d1..5983f7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,12 @@ version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" +[[package]] +name = "ascii" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" + [[package]] name = "atty" version = "0.2.14" @@ -44,16 +50,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] - [[package]] name = "base64-compat" version = "1.0.0" @@ -117,7 +113,7 @@ source = "git+https://github.com/romanz/rust-bitcoincore-rpc?rev=06ac9fa3e834413 dependencies = [ "bitcoincore-rpc-json", "jsonrpc", - "log 0.4.14", + "log", "serde", "serde_json", ] @@ -185,6 +181,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "winapi", +] + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + [[package]] name = "clang-sys" version = "1.2.0" @@ -325,8 +339,7 @@ dependencies = [ "crossbeam-channel", "dirs-next", "env_logger", - "hyper", - "log 0.4.14", + "log", "parking_lot", "prometheus", "rayon", @@ -335,6 +348,7 @@ dependencies = [ "serde_derive", "serde_json", "signal-hook", + "tiny_http", ] [[package]] @@ -345,7 +359,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime", - "log 0.4.14", + "log", "regex", "termcolor", ] @@ -374,6 +388,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -412,12 +436,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "httparse" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" - [[package]] name = "humantime" version = "1.3.0" @@ -427,30 +445,11 @@ dependencies = [ "quick-error", ] -[[package]] -name = "hyper" -version = "0.10.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" -dependencies = [ - "base64", - "httparse", - "language-tags", - "log 0.3.9", - "mime", - "num_cpus", - "time", - "traitobject", - "typeable", - "unicase", - "url", -] - [[package]] name = "idna" -version = "0.1.5" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", @@ -493,12 +492,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "1.4.0" @@ -547,15 +540,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.14", -] - [[package]] name = "log" version = "0.4.14" @@ -595,15 +579,6 @@ dependencies = [ "autocfg 1.0.1", ] -[[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -dependencies = [ - "log 0.3.9", -] - [[package]] name = "miniz_oxide" version = "0.4.4" @@ -621,7 +596,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ "memchr", - "version_check 0.9.3", + "version_check", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg 1.0.1", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg 1.0.1", ] [[package]] @@ -673,9 +667,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "percent-encoding" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "proc-macro2" @@ -941,12 +935,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "scopeguard" version = "1.1.0" @@ -1076,13 +1064,16 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.1.43" +name = "tiny_http" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "9ce51b50006056f590c9b7c3808c3bd70f0d1101666629713866c227d6e58d39" dependencies = [ - "libc", - "winapi", + "ascii", + "chrono", + "chunked_transfer", + "log", + "url", ] [[package]] @@ -1109,27 +1100,6 @@ dependencies = [ "serde", ] -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" - -[[package]] -name = "unicase" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -dependencies = [ - "version_check 0.1.5", -] - [[package]] name = "unicode-bidi" version = "0.3.5" @@ -1162,21 +1132,16 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "url" -version = "1.7.2" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ + "form_urlencoded", "idna", "matches", "percent-encoding", ] -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index 678de52..34fabe4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ build = "build.rs" [features] default = ["metrics"] -metrics = ["prometheus", "hyper"] +metrics = ["prometheus", "tiny_http"] [package.metadata.configure_me] spec = "internal/config_specification.toml" @@ -26,7 +26,6 @@ configure_me = "0.4" crossbeam-channel = "0.5" dirs-next = "2.0" env_logger = "0.7" -hyper = { version = "0.10", optional = true } log = "0.4" parking_lot = "0.11" prometheus = { version = "0.12", features = ["process"], optional = true } @@ -35,6 +34,7 @@ serde = "1.0" serde_derive = "1.0" serde_json = "1.0" signal-hook = "0.3" +tiny_http = { version = "0.8", optional = true } [dependencies.bitcoincore-rpc] # use bitcoin 0.27 (until https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/196 is merged) diff --git a/src/metrics.rs b/src/metrics.rs index 102c4f7..5e8bb44 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -1,15 +1,16 @@ #[cfg(feature = "metrics")] mod metrics_impl { use anyhow::{Context, Result}; - use hyper::server::{Handler, Listening, Request, Response, Server}; use prometheus::process_collector::ProcessCollector; use prometheus::{self, Encoder, HistogramOpts, HistogramVec, Registry}; + use tiny_http::{Response, Server}; use std::net::SocketAddr; + use crate::thread::spawn; + pub struct Metrics { reg: Registry, - listen: Listening, } impl Metrics { @@ -19,11 +20,24 @@ mod metrics_impl { reg.register(Box::new(ProcessCollector::for_self())) .expect("failed to register ProcessCollector"); - let listen = Server::http(addr)? - .handle(RegistryHandler { reg: reg.clone() }) - .with_context(|| format!("failed to serve on {}", addr))?; + let result = Self { reg }; + let reg = result.reg.clone(); + spawn("metrics", move || { + let server = Server::http(addr).unwrap(); + for request in server.incoming_requests() { + let mut buffer = vec![]; + prometheus::TextEncoder::new() + .encode(®.gather(), &mut buffer) + .context("failed to encode metrics")?; + request + .respond(Response::from_data(buffer)) + .context("failed to send HTTP response")?; + } + Ok(()) + }); + info!("serving Prometheus metrics on {}", addr); - Ok(Self { reg, listen }) + Ok(result) } pub fn histogram_vec(&self, name: &str, desc: &str, label: &str) -> Histogram { @@ -36,15 +50,6 @@ mod metrics_impl { } } - impl Drop for Metrics { - fn drop(&mut self) { - debug!("closing Prometheus server"); - if let Err(e) = self.listen.close() { - warn!("failed to stop Prometheus server: {}", e); - } - } - } - #[derive(Clone)] pub struct Histogram { hist: HistogramVec, @@ -64,28 +69,6 @@ mod metrics_impl { .observe_closure_duration(func) } } - - struct RegistryHandler { - reg: Registry, - } - - impl RegistryHandler { - fn gather(&self) -> Result> { - let mut buffer = vec![]; - prometheus::TextEncoder::new() - .encode(&self.reg.gather(), &mut buffer) - .context("failed to encode metrics")?; - Ok(buffer) - } - } - - impl Handler for RegistryHandler { - fn handle(&self, req: Request, res: Response) { - trace!("{} {}", req.method, req.uri); - let buffer = self.gather().expect("failed to gather metrics"); - res.send(&buffer).expect("failed to send metrics"); - } - } } #[cfg(feature = "metrics")] diff --git a/src/server.rs b/src/server.rs index 4dd0460..b9b4ece 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,7 +9,6 @@ use std::{ convert::TryFrom, io::{BufRead, BufReader, Write}, net::{Shutdown, TcpListener, TcpStream}, - thread, }; use crate::{ @@ -17,22 +16,9 @@ use crate::{ daemon::rpc_connect, electrum::{Client, Rpc}, signals, + thread::spawn, }; -fn spawn(name: &'static str, f: F) -> thread::JoinHandle<()> -where - F: 'static + Send + FnOnce() -> Result<()>, -{ - thread::Builder::new() - .name(name.to_owned()) - .spawn(move || { - if let Err(e) = f() { - warn!("{} thread failed: {}", name, e); - } - }) - .expect("failed to spawn a thread") -} - struct Peer { id: usize, client: Client, From fe1bfe3bc1d67a08feab56c242ce320804ed62de Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 27 Aug 2021 21:09:48 +0300 Subject: [PATCH 085/113] Probe metrics endpoint in CI --- Dockerfile | 2 +- tests/run.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4ef3ec0..48f3350 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ RUN bitcoind -version && bitcoin-cli -version ### Electrum ### # Clone latest Electrum wallet and a few test tools WORKDIR /build/ -RUN apt-get install -qqy git libsecp256k1-0 python3-cryptography python3-setuptools python3-pip jq +RUN apt-get install -qqy git libsecp256k1-0 python3-cryptography python3-setuptools python3-pip jq curl RUN git clone --recurse-submodules https://github.com/spesmilo/electrum/ && cd electrum/ && git log -1 RUN python3 -m pip install -e electrum/ diff --git a/tests/run.sh b/tests/run.sh index b8572dc..32e1b6b 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -47,6 +47,7 @@ electrs \ 2> data/electrs/regtest-debug.log & ELECTRS_PID=$! tail_log data/electrs/regtest-debug.log | grep -m1 "serving Electrum RPC" +curl localhost:24224 -o metrics.txt $ELECTRUM daemon --server localhost:60401:t -1 -vDEBUG 2> data/electrum/regtest-debug.log & ELECTRUM_PID=$! From fb88bccb0ab8df1bedb5c1fd974fa9a48b04b9de Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 28 Aug 2021 20:05:39 +0300 Subject: [PATCH 086/113] Propagate Electrum JSON RPC error --- contrib/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/client.py b/contrib/client.py index 74e74fe..5c561f2 100644 --- a/contrib/client.py +++ b/contrib/client.py @@ -17,7 +17,10 @@ class Client: msg = json.dumps(requests) + '\n' self.s.sendall(msg.encode('ascii')) response = json.loads(self.f.readline()) - return [r['result'] for r in response] + try: + return [r['result'] for r in response] + except KeyError: + raise ValueError(response) def request(method, *args): From e1ec17c5b19be745b7bcd6793ed7617ca936275f Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 28 Aug 2021 21:43:37 +0300 Subject: [PATCH 087/113] Run p2p block parsing and processing concurrently --- src/daemon.rs | 2 +- src/p2p.rs | 36 ++++++++++++++++++++++++------------ src/status.rs | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 3fd1d42..1b12805 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -188,7 +188,7 @@ impl Daemon { pub(crate) fn for_blocks(&self, blockhashes: B, func: F) -> Result<()> where B: IntoIterator, - F: FnMut(BlockHash, Block), + F: FnMut(BlockHash, Block) + Send, { self.p2p.lock().for_blocks(blockhashes, func) } diff --git a/src/p2p.rs b/src/p2p.rs index f43eb76..5bc6904 100644 --- a/src/p2p.rs +++ b/src/p2p.rs @@ -81,7 +81,7 @@ impl Connection { pub(crate) fn for_blocks(&mut self, blockhashes: B, mut func: F) -> Result<()> where B: IntoIterator, - F: FnMut(BlockHash, Block), + F: FnMut(BlockHash, Block) + Send, { let blockhashes = Vec::from_iter(blockhashes); if blockhashes.is_empty() { @@ -93,19 +93,31 @@ impl Connection { .collect(); debug!("loading {} blocks", blockhashes.len()); self.send(NetworkMessage::GetData(inv))?; - for hash in blockhashes { - match self - .recv() - .with_context(|| format!("failed to get block {}", hash))? - { - NetworkMessage::Block(block) => { - assert_eq!(block.block_hash(), hash, "got unexpected block"); + + rayon::scope(|s| { + let (tx, rx) = crossbeam_channel::bounded(10); + s.spawn(|_| { + // the loop will exit when the sender is dropped + for (hash, block) in rx { func(hash, block); } - msg => bail!("unexpected {:?}", msg), - }; - } - Ok(()) + }); + + for hash in blockhashes { + match self + .recv() + .with_context(|| format!("failed to get block {}", hash))? + { + NetworkMessage::Block(block) => { + ensure!(block.block_hash() == hash, "got unexpected block"); + tx.send((hash, block)) + .context("disconnected from block processor")?; + } + msg => bail!("unexpected {:?}", msg), + }; + } + Ok(()) + }) } pub(crate) fn get_new_headers(&mut self, chain: &Chain) -> Result> { diff --git a/src/status.rs b/src/status.rs index add5c85..5b3fe55 100644 --- a/src/status.rs +++ b/src/status.rs @@ -268,7 +268,7 @@ impl ScriptHashStatus { fn for_new_blocks(&self, blockhashes: B, daemon: &Daemon, func: F) -> Result<()> where B: IntoIterator, - F: FnMut(BlockHash, Block), + F: FnMut(BlockHash, Block) + Send, { daemon.for_blocks( blockhashes From 75e0da7168f2cf56b423ff2633b892983fc1560b Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 5 Sep 2021 19:14:17 +0300 Subject: [PATCH 088/113] Simplify txid min/max handling --- src/mempool.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/mempool.rs b/src/mempool.rs index acc916f..362229e 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -27,9 +27,16 @@ pub(crate) struct Mempool { by_funding: BTreeSet<(ScriptHash, Txid)>, by_spending: BTreeSet<(OutPoint, Txid)>, histogram: Histogram, +} - txid_min: Txid, - txid_max: Txid, +// Smallest possible txid +fn txid_min() -> Txid { + Txid::from_inner([0x00; 32]) +} + +// Largest possible txid +fn txid_max() -> Txid { + Txid::from_inner([0xFF; 32]) } impl Mempool { @@ -39,9 +46,6 @@ impl Mempool { by_funding: Default::default(), by_spending: Default::default(), histogram: Histogram::empty(), - - txid_min: Txid::from_inner([0x00; 32]), - txid_max: Txid::from_inner([0xFF; 32]), } } @@ -55,8 +59,8 @@ impl Mempool { pub(crate) fn filter_by_funding(&self, scripthash: &ScriptHash) -> Vec<&Entry> { let range = ( - Bound::Included((*scripthash, self.txid_min)), - Bound::Included((*scripthash, self.txid_max)), + Bound::Included((*scripthash, txid_min())), + Bound::Included((*scripthash, txid_max())), ); self.by_funding .range(range) @@ -66,8 +70,8 @@ impl Mempool { pub(crate) fn filter_by_spending(&self, outpoint: &OutPoint) -> Vec<&Entry> { let range = ( - Bound::Included((*outpoint, self.txid_min)), - Bound::Included((*outpoint, self.txid_max)), + Bound::Included((*outpoint, txid_min())), + Bound::Included((*outpoint, txid_max())), ); self.by_spending .range(range) From 2e5ba9b401b1f954171eae83892e1b5c3bce6c74 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 5 Sep 2021 22:09:50 +0300 Subject: [PATCH 089/113] Add docs to chain.rs --- src/chain.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/chain.rs b/src/chain.rs index 746c82c..803427b 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -5,6 +5,7 @@ use bitcoin::hashes::hex::FromHex; use bitcoin::network::constants; use bitcoin::{BlockHash, BlockHeader}; +/// A new header found, to be added to the chain at specific height pub(crate) struct NewHeader { header: BlockHeader, hash: BlockHash, @@ -36,6 +37,7 @@ pub struct Chain { } impl Chain { + // create an empty chain pub fn new(network: constants::Network) -> Self { let genesis_header_hex = match network { constants::Network::Bitcoin => "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c", @@ -48,10 +50,11 @@ impl Chain { assert_eq!(genesis.prev_blockhash, BlockHash::default()); Self { headers: vec![(genesis.block_hash(), genesis)], - heights: std::iter::once((genesis.block_hash(), 0)).collect(), + heights: std::iter::once((genesis.block_hash(), 0)).collect(), // genesis header @ zero height } } + /// Load the chain from a collecion of headers, up to the given tip pub(crate) fn load(&mut self, headers: Vec, tip: BlockHash) { let genesis_hash = self.headers[0].0; @@ -72,18 +75,23 @@ impl Chain { self.update(new_headers.zip(1..).map(NewHeader::from).collect()) } + /// Get the block hash at specified height (if exists) pub(crate) fn get_block_hash(&self, height: usize) -> Option { self.headers.get(height).map(|(hash, _header)| *hash) } + /// Get the block header at specified height (if exists) pub(crate) fn get_block_header(&self, height: usize) -> Option<&BlockHeader> { self.headers.get(height).map(|(_hash, header)| header) } + /// Get the block height given the specified hash (if exists) pub(crate) fn get_block_height(&self, blockhash: &BlockHash) -> Option { self.heights.get(blockhash).copied() } + /// Update the chain with a list of new headers (possibly a reorg) + /// Note that we cannot shorten a chain (e.g. by dropping ) pub(crate) fn update(&mut self, headers: Vec) { if let Some(first_height) = headers.first().map(|h| h.height) { for (hash, _header) in self.headers.drain(first_height..) { @@ -103,14 +111,18 @@ impl Chain { } } + /// Best block hash pub(crate) fn tip(&self) -> BlockHash { self.headers.last().expect("empty chain").0 } + /// Number of blocks (excluding genesis block) pub(crate) fn height(&self) -> usize { self.headers.len() - 1 } + /// List of block hashes for efficient fork detection and block/header sync + /// see https://en.bitcoin.it/wiki/Protocol_documentation#getblocks pub(crate) fn locator(&self) -> Vec { let mut result = vec![]; let mut index = self.headers.len() - 1; From 3090866fdd9d90ffde55e723fe743319c0e1cf7c Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 8 Sep 2021 18:46:28 +0300 Subject: [PATCH 090/113] Drop helper binaries and some APIs --- query.sh | 15 --------------- src/bin/query.rs | 43 ------------------------------------------- src/bin/sync.rs | 13 ------------- src/lib.rs | 2 +- src/status.rs | 8 +------- src/tracker.rs | 4 ++-- 6 files changed, 4 insertions(+), 81 deletions(-) delete mode 100755 query.sh delete mode 100644 src/bin/query.rs delete mode 100644 src/bin/sync.rs diff --git a/query.sh b/query.sh deleted file mode 100755 index 26054bd..0000000 --- a/query.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -eux -cd `dirname $0` - -cargo fmt --all -cargo build --all --release - -NETWORK=$1 -shift - -CMD="target/release/query --network $NETWORK --db-dir ./db2 --daemon-dir $HOME/.bitcoin" -export RUST_LOG=${RUST_LOG-info} -$CMD $* - -# use SIGINT to quit diff --git a/src/bin/query.rs b/src/bin/query.rs deleted file mode 100644 index a57e700..0000000 --- a/src/bin/query.rs +++ /dev/null @@ -1,43 +0,0 @@ -#[macro_use] -extern crate log; - -use anyhow::Result; -use bitcoin::{Address, Amount}; - -use std::collections::BTreeMap; -use std::str::FromStr; - -use electrs::{Balance, Cache, Config, Daemon, ScriptHash, ScriptHashStatus, Tracker}; - -fn main() -> Result<()> { - let config = Config::from_args(); - let addresses = config - .args - .iter() - .map(|a| Address::from_str(a).expect("invalid address")); - - let cache = Cache::default(); - let daemon = Daemon::connect(&config)?; - let mut tracker = Tracker::new(&config)?; - let mut map: BTreeMap = addresses - .map(|addr| { - let status = ScriptHashStatus::new(ScriptHash::new(&addr.script_pubkey())); - (addr, status) - }) - .collect(); - - loop { - tracker.sync(&daemon)?; - let mut total = Amount::ZERO; - for (addr, status) in map.iter_mut() { - tracker.update_scripthash_status(status, &daemon, &cache)?; - let balance = tracker.get_balance(status, &cache); - if balance != Balance::default() { - info!("{} has {}", addr, balance.confirmed()); - } - total += balance.confirmed(); - } - info!("total: {}", total); - std::thread::sleep(config.wait_duration); - } -} diff --git a/src/bin/sync.rs b/src/bin/sync.rs deleted file mode 100644 index 318e354..0000000 --- a/src/bin/sync.rs +++ /dev/null @@ -1,13 +0,0 @@ -use anyhow::Result; - -use electrs::{Config, Daemon, Tracker}; - -fn main() -> Result<()> { - let config = Config::from_args(); - let daemon = Daemon::connect(&config)?; - let mut tracker = Tracker::new(&config)?; - loop { - tracker.sync(&daemon)?; - std::thread::sleep(config.wait_duration); - } -} diff --git a/src/lib.rs b/src/lib.rs index 95935f3..e5abe15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ pub use { config::Config, daemon::Daemon, electrum::{Client, Rpc}, - status::{Balance, ScriptHashStatus}, + status::ScriptHashStatus, tracker::Tracker, types::ScriptHash, }; diff --git a/src/status.rs b/src/status.rs index 5b3fe55..73a8639 100644 --- a/src/status.rs +++ b/src/status.rs @@ -131,17 +131,11 @@ enum BalanceEntry { } #[derive(Default, Eq, PartialEq)] -pub struct Balance { +pub(crate) struct Balance { pub(crate) confirmed: Amount, pub(crate) mempool_delta: SignedAmount, } -impl Balance { - pub fn confirmed(&self) -> Amount { - self.confirmed - } -} - #[derive(Default)] struct Total { funded: Amount, diff --git a/src/tracker.rs b/src/tracker.rs index 8337bcd..177fedf 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -65,7 +65,7 @@ impl Tracker { Ok(()) } - pub fn update_scripthash_status( + pub(crate) fn update_scripthash_status( &self, status: &mut ScriptHashStatus, daemon: &Daemon, @@ -76,7 +76,7 @@ impl Tracker { Ok(prev_statushash != status.statushash()) } - pub fn get_balance(&self, status: &ScriptHashStatus, cache: &Cache) -> Balance { + pub(crate) fn get_balance(&self, status: &ScriptHashStatus, cache: &Cache) -> Balance { let get_amount_fn = |outpoint: OutPoint| { cache .get_tx(&outpoint.txid, |tx| { From 5332cc677f6abe2755b87573b997df311310df37 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 8 Sep 2021 07:47:39 +0300 Subject: [PATCH 091/113] Refactor and document status.rs --- src/status.rs | 148 +++++++++++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 62 deletions(-) diff --git a/src/status.rs b/src/status.rs index 73a8639..9540e8b 100644 --- a/src/status.rs +++ b/src/status.rs @@ -19,32 +19,36 @@ use crate::{ types::{ScriptHash, StatusHash}, }; -#[derive(Default)] -struct Entry { - outputs: Vec, - spent: Vec, -} - +/// Given a scripthash, store relevant inputs and outputs of a specific transaction struct TxEntry { txid: Txid, - outputs: Vec, - spent: Vec, + outputs: Vec, // relevant funded output indices + spent: Vec, // relevant spent outpoints } impl TxEntry { - fn new(txid: Txid, entry: Entry) -> Self { + fn new(txid: Txid) -> Self { Self { txid, - outputs: entry.outputs, - spent: entry.spent, + outputs: Vec::new(), + spent: Vec::new(), } } + /// Relevant (scripthash-wise) funded outpoints fn funding(&self) -> impl Iterator + '_ { make_outpoints(&self.txid, &self.outputs) } + + /// Relevant (scripthash-wise) spent outpoints + fn spending(&self) -> impl Iterator + '_ { + self.spent.iter().copied() + } } +// Confirmation height of a transaction or its mempool state: +// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history +// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-mempool enum Height { Confirmed { height: usize }, Unconfirmed { has_unconfirmed_inputs: bool }, @@ -79,6 +83,9 @@ impl std::fmt::Display for Height { } } +// A single history entry: +// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history +// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-mempool #[derive(Serialize)] pub(crate) struct HistoryEntry { #[serde(rename = "tx_hash")] @@ -118,11 +125,12 @@ impl HistoryEntry { /// ScriptHash subscription status pub struct ScriptHashStatus { - scripthash: ScriptHash, - tip: BlockHash, - statushash: Option, - confirmed: HashMap>, - mempool: Vec, + scripthash: ScriptHash, // specfic scripthash to be queried + tip: BlockHash, // used for skipping confirmed entries' sync + confirmed: HashMap>, // confirmed entries, partitioned per block (may contain stale blocks) + mempool: Vec, // unconfirmed entries + history: Vec, // computed from confirmed and mempool entries + statushash: Option, // computed from history } enum BalanceEntry { @@ -130,6 +138,7 @@ enum BalanceEntry { Spent(OutPoint), } +/// Specific scripthash balance #[derive(Default, Eq, PartialEq)] pub(crate) struct Balance { pub(crate) confirmed: Amount, @@ -161,21 +170,21 @@ impl Total { } } -fn make_outpoints<'a>(txid: &'a Txid, outputs: &'a [u32]) -> impl Iterator + 'a { - outputs.iter().map(move |vout| OutPoint::new(*txid, *vout)) -} - impl ScriptHashStatus { + /// Return non-synced (empty) status for a given script hash. pub fn new(scripthash: ScriptHash) -> Self { Self { scripthash, tip: BlockHash::default(), - statushash: None, confirmed: HashMap::new(), mempool: Vec::new(), + history: Vec::new(), + statushash: None, } } + /// Iterate through confirmed TxEntries with their corresponding block heights. + /// Skip entries from stale blocks. fn confirmed_entries<'a>( &'a self, chain: &'a Chain, @@ -189,7 +198,8 @@ impl ScriptHashStatus { }) } - fn funding_confirmed(&self, chain: &Chain) -> HashSet { + /// Collect all funded and confirmed outpoints (as a set). + fn confirmed_outpoints(&self, chain: &Chain) -> HashSet { self.confirmed_entries(chain) .flat_map(|(_height, entries)| entries.iter().flat_map(TxEntry::funding)) .collect() @@ -202,9 +212,9 @@ impl ScriptHashStatus { fn to_balance_entries<'a>( entries: impl Iterator + 'a, ) -> impl Iterator + 'a { - entries.flat_map(|entry| { - let funded = entry.funding().map(BalanceEntry::Funded); - let spent = entry.spent.iter().copied().map(BalanceEntry::Spent); + entries.flat_map(|e| { + let funded = e.funding().map(BalanceEntry::Funded); + let spent = e.spending().map(BalanceEntry::Spent); funded.chain(spent) }) } @@ -229,12 +239,13 @@ impl ScriptHashStatus { } pub(crate) fn get_history(&self, chain: &Chain, mempool: &Mempool) -> Vec { - let mut result = self.get_confirmed(chain); - result.extend(self.get_mempool(mempool)); + let mut result = self.get_confirmed_history(chain); + result.extend(self.get_mempool_history(mempool)); result } - fn get_confirmed(&self, chain: &Chain) -> Vec { + /// Collect all confirmed history entries (in block order). + fn get_confirmed_history(&self, chain: &Chain) -> Vec { self.confirmed_entries(chain) .collect::>() .into_iter() @@ -246,7 +257,8 @@ impl ScriptHashStatus { .collect() } - fn get_mempool(&self, mempool: &Mempool) -> Vec { + /// Collect all mempool history entries (keeping transactions with unconfirmed parents last). + fn get_mempool_history(&self, mempool: &Mempool) -> Vec { let mut entries = self .mempool .iter() @@ -259,6 +271,7 @@ impl ScriptHashStatus { .collect() } + /// Apply func only on the new blocks (fetched from daemon). fn for_new_blocks(&self, blockhashes: B, daemon: &Daemon, func: F) -> Result<()> where B: IntoIterator, @@ -272,6 +285,8 @@ impl ScriptHashStatus { ) } + /// Get funding and spending entries from new blocks. + /// Also cache relevant transactions and their merkle proofs. fn sync_confirmed( &self, index: &Index, @@ -279,8 +294,8 @@ impl ScriptHashStatus { cache: &Cache, outpoints: &mut HashSet, ) -> Result>> { - type PosTxid = (u32, Txid); - let mut result = HashMap::>::new(); + type TxPosition = usize; // transaction position within a block + let mut result = HashMap::>::new(); let funding_blockhashes = index.limit_result(index.filter_by_funding(self.scripthash))?; self.for_new_blocks(funding_blockhashes, daemon, |blockhash, block| { @@ -296,8 +311,8 @@ impl ScriptHashStatus { result .entry(blockhash) .or_default() - .entry((u32::try_from(pos).unwrap(), *txid)) - .or_default() + .entry(pos) + .or_insert_with(|| TxEntry::new(*txid)) .outputs = funding_outputs; } })?; @@ -317,8 +332,8 @@ impl ScriptHashStatus { result .entry(blockhash) .or_default() - .entry((u32::try_from(pos).unwrap(), *txid)) - .or_default() + .entry(pos) + .or_insert_with(|| TxEntry::new(*txid)) .spent = spent_outpoints; } })?; @@ -326,29 +341,35 @@ impl ScriptHashStatus { Ok(result .into_iter() .map(|(blockhash, entries_map)| { + // sort transactions by their position in a block let sorted_entries = entries_map .into_iter() - .collect::>() + .collect::>() .into_iter() - .map(|((_pos, txid), entry)| TxEntry::new(txid, entry)) + .map(|(_pos, entry)| entry) .collect::>(); (blockhash, sorted_entries) }) .collect()) } + /// Get funding and spending entries from current mempool. + /// Also cache relevant transactions. fn sync_mempool( &self, mempool: &Mempool, cache: &Cache, outpoints: &mut HashSet, ) -> Vec { - let mut result = HashMap::::new(); + let mut result = HashMap::::new(); for entry in mempool.filter_by_funding(&self.scripthash) { let funding_outputs = filter_outputs(&entry.tx, &self.scripthash); assert!(!funding_outputs.is_empty()); outpoints.extend(make_outpoints(&entry.txid, &funding_outputs)); - result.entry(entry.txid).or_default().outputs = funding_outputs; + result + .entry(entry.txid) + .or_insert_with(|| TxEntry::new(entry.txid)) + .outputs = funding_outputs; cache.add_tx(entry.txid, || entry.tx.clone()); } for entry in outpoints @@ -357,31 +378,17 @@ impl ScriptHashStatus { { let spent_outpoints = filter_inputs(&entry.tx, outpoints); assert!(!spent_outpoints.is_empty()); - result.entry(entry.txid).or_default().spent = spent_outpoints; + result + .entry(entry.txid) + .or_insert_with(|| TxEntry::new(entry.txid)) + .spent = spent_outpoints; cache.add_tx(entry.txid, || entry.tx.clone()); } - result - .into_iter() - .map(|(txid, entry)| TxEntry::new(txid, entry)) - .collect() - } - - fn compute_status_hash(&self, chain: &Chain, mempool: &Mempool) -> Option { - let confirmed = self.get_confirmed(chain); - let mempool = self.get_mempool(mempool); - if confirmed.is_empty() && mempool.is_empty() { - return None; - } - let mut engine = StatusHash::engine(); - for entry in confirmed { - entry.hash(&mut engine); - } - for entry in mempool { - entry.hash(&mut engine); - } - Some(StatusHash::from_engine(engine)) + result.into_iter().map(|(_txid, entry)| entry).collect() } + /// Sync with currently confirmed txs and mempool, downloading non-cached transactions via p2p protocol. + /// After a successful sync, scripthash status is updated. pub(crate) fn sync( &mut self, index: &Index, @@ -389,7 +396,7 @@ impl ScriptHashStatus { daemon: &Daemon, cache: &Cache, ) -> Result<()> { - let mut outpoints: HashSet = self.funding_confirmed(index.chain()); + let mut outpoints: HashSet = self.confirmed_outpoints(index.chain()); let new_tip = index.chain().tip(); if self.tip != new_tip { @@ -408,15 +415,21 @@ impl ScriptHashStatus { if !self.mempool.is_empty() { debug!("{} mempool transactions", self.mempool.len()); } - self.statushash = self.compute_status_hash(index.chain(), mempool); + self.history = self.get_history(index.chain(), mempool); + self.statushash = compute_status_hash(&self.history); Ok(()) } + /// Get current status hash. pub fn statushash(&self) -> Option { self.statushash } } +fn make_outpoints<'a>(txid: &'a Txid, outputs: &'a [u32]) -> impl Iterator + 'a { + outputs.iter().map(move |vout| OutPoint::new(*txid, *vout)) +} + fn filter_outputs(tx: &Transaction, scripthash: &ScriptHash) -> Vec { let outputs = tx.output.iter().zip(0u32..); outputs @@ -443,6 +456,17 @@ fn filter_inputs(tx: &Transaction, outpoints: &HashSet) -> Vec Option { + if history.is_empty() { + return None; + } + let mut engine = StatusHash::engine(); + for entry in history { + entry.hash(&mut engine); + } + Some(StatusHash::from_engine(engine)) +} + #[cfg(test)] mod tests { use super::HistoryEntry; From 269e7563834ee39be0e7918962939560aafcb0d4 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 8 Sep 2021 22:03:56 +0300 Subject: [PATCH 092/113] Next release will be 0.9.0-rc1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5983f7a..3269666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,7 +329,7 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "electrs" -version = "0.9.0" +version = "0.9.0-rc1" dependencies = [ "anyhow", "bitcoin", diff --git a/Cargo.toml b/Cargo.toml index 34fabe4..2b4861f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "electrs" -version = "0.9.0" +version = "0.9.0-rc1" authors = ["Roman Zeyde "] description = "An efficient re-implementation of Electrum Server in Rust" license = "MIT" From 2f24d0774abdb7bd37b76c277738e148529d7d88 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 10 Sep 2021 14:28:10 +0300 Subject: [PATCH 093/113] Downgrade librocksdb-sys to 6.11.4 --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3269666..953d648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,8 +522,8 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "6.15.4" -source = "git+https://github.com/romanz/rust-rocksdb?rev=4554d19b2ff2e34493564b4d868454097c74b693#4554d19b2ff2e34493564b4d868454097c74b693" +version = "6.11.4" +source = "git+https://github.com/romanz/rust-rocksdb?rev=2023b18a7b83fc47b5bc950b5322a2284b771162#2023b18a7b83fc47b5bc950b5322a2284b771162" dependencies = [ "bindgen", "cc", @@ -911,7 +911,7 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "rocksdb" version = "0.15.0" -source = "git+https://github.com/romanz/rust-rocksdb?rev=4554d19b2ff2e34493564b4d868454097c74b693#4554d19b2ff2e34493564b4d868454097c74b693" +source = "git+https://github.com/romanz/rust-rocksdb?rev=2023b18a7b83fc47b5bc950b5322a2284b771162#2023b18a7b83fc47b5bc950b5322a2284b771162" dependencies = [ "libc", "librocksdb-sys", diff --git a/Cargo.toml b/Cargo.toml index 2b4861f..f9ec76e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ rev = "06ac9fa3e834413f7afeaed322cf8098d876e4a0" [dependencies.rocksdb] # support building with Rust 1.41.1 and workaround https://github.com/romanz/electrs/issues/403 git = "https://github.com/romanz/rust-rocksdb" -rev = "4554d19b2ff2e34493564b4d868454097c74b693" +rev = "2023b18a7b83fc47b5bc950b5322a2284b771162" default-features = false features = ["zstd"] From f5df00f6316f219a16be1d97927593401ec9a0bc Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 11 Sep 2021 11:01:05 +0300 Subject: [PATCH 094/113] Allow single-sync invocation using --sync-once commandline flag --- internal/config_specification.toml | 4 ++++ src/bin/electrs.rs | 4 +++- src/config.rs | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/config_specification.toml b/internal/config_specification.toml index e3a73f3..7b937e9 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -85,6 +85,10 @@ default = "10" name = "ignore_mempool" doc = "Don't sync mempool - queries will show only confirmed transactions." +[[switch]] +name = "sync_once" +doc = "Exit after the initial sync is over (don't start Electrum server)." + [[param]] name = "index_lookup_limit" type = "usize" diff --git a/src/bin/electrs.rs b/src/bin/electrs.rs index 0095ffc..37b58d1 100644 --- a/src/bin/electrs.rs +++ b/src/bin/electrs.rs @@ -9,7 +9,9 @@ fn main() -> Result<()> { tracker .sync(&Daemon::connect(&config)?) .context("initial sync failed")?; - + if config.sync_once { + return Ok(()); + } // re-connect after initial sync (due to possible timeout during compaction) server::run(&config, Rpc::new(&config, tracker)?).context("server failed") } diff --git a/src/config.rs b/src/config.rs index 8d5bbb5..c92572f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -133,6 +133,7 @@ pub struct Config { pub index_batch_size: usize, pub index_lookup_limit: Option, pub ignore_mempool: bool, + pub sync_once: bool, pub server_banner: String, pub args: Vec, } @@ -294,6 +295,7 @@ impl Config { index_batch_size: config.index_batch_size, index_lookup_limit, ignore_mempool: config.ignore_mempool, + sync_once: config.sync_once, server_banner: config.server_banner, args: args.map(|a| a.into_string().unwrap()).collect(), }; From 8d12df353c2ec3da385aa1a60dc71faafacfdab3 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 12 Sep 2021 16:17:39 +0300 Subject: [PATCH 095/113] Use `try_from` for explicit type conversion (instead of `try_into`) --- src/mempool.rs | 4 ++-- src/tracker.rs | 4 ++-- src/types.rs | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/mempool.rs b/src/mempool.rs index 362229e..fc1954b 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -1,7 +1,7 @@ use anyhow::Result; use std::collections::{BTreeSet, HashMap, HashSet}; -use std::convert::TryInto; +use std::convert::TryFrom; use std::iter::FromIterator; use std::ops::Bound; @@ -180,7 +180,7 @@ impl Histogram { let mut bins = [0; Self::SIZE]; for (fee, vsize) in items { let fee_rate = fee.as_sat() / vsize; - let index: usize = fee_rate.leading_zeros().try_into().unwrap(); + let index = usize::try_from(fee_rate.leading_zeros()).unwrap(); // skip transactions with too low fee rate (<1 sat/vB) if let Some(bin) = bins.get_mut(index) { *bin += vsize diff --git a/src/tracker.rs b/src/tracker.rs index 177fedf..7bdaedd 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use bitcoin::{BlockHash, OutPoint, Txid}; -use std::convert::TryInto; +use std::convert::TryFrom; use std::path::Path; use crate::{ @@ -80,7 +80,7 @@ impl Tracker { let get_amount_fn = |outpoint: OutPoint| { cache .get_tx(&outpoint.txid, |tx| { - let vout: usize = outpoint.vout.try_into().unwrap(); + let vout = usize::try_from(outpoint.vout).unwrap(); bitcoin::Amount::from_sat(tx.output[vout].value) }) .expect("missing tx") diff --git a/src/types.rs b/src/types.rs index 1f21126..812d7b4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use std::convert::TryInto; +use std::convert::TryFrom; use bitcoin::{ consensus::encode::{deserialize, serialize, Decodable, Encodable}, @@ -84,7 +84,7 @@ impl ScriptHashRow { pub(crate) fn new(scripthash: ScriptHash, height: usize) -> Self { Self { prefix: scripthash.prefix(), - height: height.try_into().expect("invalid height"), + height: Height::try_from(height).expect("invalid height"), } } @@ -97,7 +97,7 @@ impl ScriptHashRow { } pub(crate) fn height(&self) -> usize { - self.height.try_into().expect("invalid height") + usize::try_from(self.height).expect("invalid height") } } @@ -121,8 +121,8 @@ struct SpendingPrefix { impl_consensus_encoding!(SpendingPrefix, prefix); fn spending_prefix(prev: OutPoint) -> SpendingPrefix { - let txid_prefix = &prev.txid[..HASH_PREFIX_LEN]; - let value = u64::from_be_bytes(txid_prefix.try_into().unwrap()); + let txid_prefix = <[u8; HASH_PREFIX_LEN]>::try_from(&prev.txid[..HASH_PREFIX_LEN]).unwrap(); + let value = u64::from_be_bytes(txid_prefix); let value = value.wrapping_add(prev.vout.into()); SpendingPrefix { prefix: value.to_be_bytes(), @@ -145,7 +145,7 @@ impl SpendingPrefixRow { pub(crate) fn new(outpoint: OutPoint, height: usize) -> Self { Self { prefix: spending_prefix(outpoint), - height: height.try_into().expect("invalid height"), + height: Height::try_from(height).expect("invalid height"), } } @@ -158,7 +158,7 @@ impl SpendingPrefixRow { } pub(crate) fn height(&self) -> usize { - self.height.try_into().expect("invalid height") + usize::try_from(self.height).expect("invalid height") } } @@ -193,7 +193,7 @@ impl TxidRow { pub(crate) fn new(txid: Txid, height: usize) -> Self { Self { prefix: txid_prefix(&txid), - height: height.try_into().expect("invalid height"), + height: Height::try_from(height).expect("invalid height"), } } @@ -206,7 +206,7 @@ impl TxidRow { } pub(crate) fn height(&self) -> usize { - self.height.try_into().expect("invalid height") + usize::try_from(self.height).expect("invalid height") } } From 96be60e890520a2dbbe900706eebeffa8427b6ef Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 12 Sep 2021 23:27:48 +0300 Subject: [PATCH 096/113] Update Cargo dependencies Keep the following (otherwise the build breaks): cargo update -p clang-sys --precise 1.2.1 cargo update -p bitflags --precise 1.2.1 --- Cargo.lock | 111 ++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 953d648..9438192 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,9 +17,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.41" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] name = "ascii" @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.68" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" dependencies = [ "jobserver", ] @@ -201,9 +201,9 @@ checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" [[package]] name = "clang-sys" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +checksum = "81cf2cc85830eae84823884db23c5306442a6c3d5bfd3beb2f2a2c829faa1816" dependencies = [ "glob", "libc", @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "configure_me_codegen" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97f64541226ea2aaaad89ce86ec9453b1e913a54d71eb987ab9d8ed52dacce2f" +checksum = "a7ddbd860cbff075ceadf4714922a73afbe8bf53d9ed9add6c6b733feeb6f3c7" dependencies = [ "cargo_toml", "fmt2io", @@ -366,9 +366,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +checksum = "80edafed416a46fb378521624fab1cfa2eb514784fd8921adbe8a8d8321da811" dependencies = [ "cfg-if 1.0.0", "crc32fast", @@ -458,24 +458,24 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "itoa" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "jobserver" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" dependencies = [ "libc", ] @@ -506,9 +506,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.97" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "libloading" @@ -533,9 +533,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ "scopeguard", ] @@ -560,15 +560,15 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memoffset" @@ -630,9 +630,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -641,9 +641,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if 1.0.0", "instant", @@ -673,9 +673,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" dependencies = [ "unicode-xid", ] @@ -713,9 +713,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.24.1" +version = "2.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db50e77ae196458ccd3dc58a31ea1a90b0698ab1b7928d89f644c25d72070267" +checksum = "23129d50f2c9355ced935fce8a08bd706ee2e7ce2b3b33bf61dace0e379ac63a" [[package]] name = "quick-error" @@ -874,9 +874,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] @@ -963,18 +963,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.126" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2", "quote", @@ -983,9 +983,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" dependencies = [ "itoa", "ryu", @@ -1000,9 +1000,9 @@ checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "signal-hook" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" dependencies = [ "libc", "signal-hook-registry", @@ -1025,9 +1025,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" dependencies = [ "proc-macro2", "quote", @@ -1045,18 +1045,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.25" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" +checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.25" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" +checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" dependencies = [ "proc-macro2", "quote", @@ -1078,9 +1078,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "5241dd6f21443a3606b432718b166d3cedc962fd4b8bea54a8bc7f514ebda986" dependencies = [ "tinyvec_macros", ] @@ -1102,12 +1102,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" [[package]] name = "unicode-normalization" @@ -1120,9 +1117,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-xid" From 382d71787a1fd0a4835079c0a62ce80722670b36 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 18 Aug 2021 16:59:27 +0300 Subject: [PATCH 097/113] Update documentation for the upcoming 0.9 version --- .github/ISSUE_TEMPLATE/bug.md | 60 +++++++++++ .github/ISSUE_TEMPLATE/build_problem.md | 50 +++++++++ .github/ISSUE_TEMPLATE/config_problem.md | 46 +++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++ README.md | 19 ++-- RELEASE-NOTES.md | 10 ++ doc/config_example.toml | 29 ++++++ doc/schema.md | 24 ++++- doc/usage.md | 118 +++++++++++++--------- 9 files changed, 324 insertions(+), 52 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/build_problem.md create mode 100644 .github/ISSUE_TEMPLATE/config_problem.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 doc/config_example.toml diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..e0fe364 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,60 @@ +--- +name: Bug report +about: Generic bug report +title: 'Bug:' +labels: bug +assignees: '' + +--- + + + +**Describe the bug** +A clear and concise description of what the bug is. + +**Electrs version** +Which version of `electrs` do you use? Please try to use newest version if possible. +If it's not the newest version why you can't try the newest one? + +**To Reproduce** +Steps to reproduce the behavior: +1. Configure and start electrs +2. Connect with electrum client XYZ +3. Wait +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Configuration** + + +