From e4ccf8f18bdde08902eb475b724d5ea6ce2cd142 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Sep 2022 11:59:27 +0200 Subject: [PATCH 01/18] feat: dynamic contants --- Cargo.lock | 233 ++++++++++++++++++++++++++++++--------- Cargo.toml | 2 + artifacts/polkadot.scale | Bin 350605 -> 351107 bytes src/chain.rs | 103 +---------------- src/helpers.rs | 74 +++++++------ src/lib.rs | 1 + src/main.rs | 3 +- src/opt.rs | 3 - 8 files changed, 234 insertions(+), 185 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b6e82622..c0cf356e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -182,6 +182,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" + [[package]] name = "beef" version = "0.5.2" @@ -314,6 +320,15 @@ version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +[[package]] +name = "cfg-expr" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +dependencies = [ + "smallvec", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -744,7 +759,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "frame-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "frame-support", "frame-system", @@ -767,7 +782,7 @@ dependencies = [ [[package]] name = "frame-election-provider-solution-type" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -778,7 +793,7 @@ dependencies = [ [[package]] name = "frame-election-provider-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "frame-election-provider-solution-type", "frame-support", @@ -806,7 +821,7 @@ dependencies = [ [[package]] name = "frame-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "bitflags", "frame-metadata", @@ -831,16 +846,19 @@ dependencies = [ "sp-state-machine 0.12.0 (git+https://github.com/paritytech/substrate?branch=master)", "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", "sp-tracing 5.0.0 (git+https://github.com/paritytech/substrate?branch=master)", + "sp-weights", "tt-call", ] [[package]] name = "frame-support-procedural" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "Inflector", + "cfg-expr", "frame-support-procedural-tools", + "itertools", "proc-macro2", "quote", "syn", @@ -849,7 +867,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate", @@ -861,7 +879,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "proc-macro2", "quote", @@ -871,7 +889,7 @@ dependencies = [ [[package]] name = "frame-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "frame-support", "log", @@ -883,6 +901,7 @@ dependencies = [ "sp-runtime 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", "sp-version", + "sp-weights", ] [[package]] @@ -1552,6 +1571,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + [[package]] name = "merlin" version = "2.0.1" @@ -1637,6 +1662,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.2" @@ -1673,7 +1709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg", - "num-bigint", + "num-bigint 0.2.6", "num-integer", "num-traits", ] @@ -1685,6 +1721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", + "num-bigint 0.4.3", "num-integer", "num-traits", ] @@ -1751,13 +1788,14 @@ checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" [[package]] name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "frame-benchmarking", "frame-election-provider-support", "frame-support", "frame-system", "log", + "pallet-election-provider-support-benchmarking", "parity-scale-codec", "rand 0.7.3", "scale-info", @@ -1771,10 +1809,23 @@ dependencies = [ "strum", ] +[[package]] +name = "pallet-election-provider-support-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-system", + "parity-scale-codec", + "sp-npos-elections", + "sp-runtime 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", +] + [[package]] name = "pallet-transaction-payment" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "frame-support", "frame-system", @@ -1846,6 +1897,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +[[package]] +name = "parity-wasm" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1925,6 +1982,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -2398,6 +2466,7 @@ checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", "generic-array 0.14.5", + "pkcs8", "subtle", "zeroize", ] @@ -2640,7 +2709,7 @@ dependencies = [ [[package]] name = "sp-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "hash-db", "log", @@ -2658,7 +2727,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "blake2", "proc-macro-crate", @@ -2684,7 +2753,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "parity-scale-codec", "scale-info", @@ -2713,7 +2782,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "integer-sqrt", "num-traits", @@ -2768,18 +2837,18 @@ dependencies = [ "substrate-bip39", "thiserror", "tiny-bip39", - "wasmi", + "wasmi 0.9.1", "zeroize", ] [[package]] name = "sp-core" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "base58", "bitflags", - "blake2-rfc", + "blake2", "byteorder", "dyn-clonable", "ed25519-zebra", @@ -2814,7 +2883,7 @@ dependencies = [ "substrate-bip39", "thiserror", "tiny-bip39", - "wasmi", + "wasmi 0.13.0", "zeroize", ] @@ -2835,7 +2904,7 @@ dependencies = [ [[package]] name = "sp-core-hashing" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "blake2", "byteorder", @@ -2849,7 +2918,7 @@ dependencies = [ [[package]] name = "sp-core-hashing-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "proc-macro2", "quote", @@ -2871,7 +2940,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "proc-macro2", "quote", @@ -2893,7 +2962,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "environmental", "parity-scale-codec", @@ -2904,7 +2973,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -2944,7 +3013,7 @@ dependencies = [ [[package]] name = "sp-io" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "bytes", "futures", @@ -2987,7 +3056,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "async-trait", "futures", @@ -3003,7 +3072,7 @@ dependencies = [ [[package]] name = "sp-npos-elections" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "parity-scale-codec", "scale-info", @@ -3028,7 +3097,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "backtrace", "lazy_static", @@ -3061,7 +3130,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "either", "hash256-std-hasher", @@ -3078,6 +3147,7 @@ dependencies = [ "sp-core 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", "sp-io 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", + "sp-weights", ] [[package]] @@ -3101,7 +3171,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -3132,7 +3202,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "Inflector", "proc-macro-crate", @@ -3144,7 +3214,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "parity-scale-codec", "scale-info", @@ -3179,7 +3249,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "hash-db", "log", @@ -3207,7 +3277,7 @@ checksum = "14804d6069ee7a388240b665f17908d98386ffb0b5d39f89a4099fc7a2a4c03f" [[package]] name = "sp-std" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" [[package]] name = "sp-storage" @@ -3226,7 +3296,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "impl-serde", "parity-scale-codec", @@ -3252,7 +3322,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "parity-scale-codec", "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", @@ -3280,7 +3350,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "ahash", "hash-db", @@ -3303,11 +3373,11 @@ dependencies = [ [[package]] name = "sp-version" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "impl-serde", "parity-scale-codec", - "parity-wasm", + "parity-wasm 0.45.0", "scale-info", "serde", "sp-core-hashing-proc-macro", @@ -3320,7 +3390,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -3338,19 +3408,35 @@ dependencies = [ "log", "parity-scale-codec", "sp-std 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmi", + "wasmi 0.9.1", ] [[package]] name = "sp-wasm-interface" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" dependencies = [ "impl-trait-for-tuples", "log", "parity-scale-codec", "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", - "wasmi", + "wasmi 0.13.0", +] + +[[package]] +name = "sp-weights" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic 5.0.0 (git+https://github.com/paritytech/substrate?branch=master)", + "sp-core 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", + "sp-debug-derive 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", ] [[package]] @@ -3359,11 +3445,21 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "ss58-registry" -version = "1.23.0" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ef98aedad3dc52e10995e7ed15f1279e11d4da35795f5dac7305742d0feb66" +checksum = "b0837b5d62f42082c9d56cd946495ae273a3c68083b637b9153341d5e465146d" dependencies = [ "Inflector", "num-format", @@ -3391,10 +3487,12 @@ dependencies = [ "pallet-election-provider-multi-phase", "pallet-transaction-payment", "parity-scale-codec", + "parking_lot", "paste", "pin-project-lite", "prometheus", "scale-info", + "scale-value", "serde", "serde_json", "sp-io 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", @@ -4046,11 +4144,22 @@ checksum = "ca00c5147c319a8ec91ec1a0edbec31e566ce2c9cc93b3f9bb86a9efd0eb795d" dependencies = [ "downcast-rs", "libc", - "memory_units", + "memory_units 0.3.0", "num-rational 0.2.4", "num-traits", - "parity-wasm", - "wasmi-validation", + "parity-wasm 0.42.2", + "wasmi-validation 0.4.1", +] + +[[package]] +name = "wasmi" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc13b3c219ca9aafeec59150d80d89851df02e0061bc357b4d66fc55a8d38787" +dependencies = [ + "parity-wasm 0.45.0", + "wasmi-validation 0.5.0", + "wasmi_core", ] [[package]] @@ -4059,7 +4168,29 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" dependencies = [ - "parity-wasm", + "parity-wasm 0.42.2", +] + +[[package]] +name = "wasmi-validation" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ff416ad1ff0c42e5a926ed5d5fab74c0f098749aa0ad8b2a34b982ce0e867b" +dependencies = [ + "parity-wasm 0.45.0", +] + +[[package]] +name = "wasmi_core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a088e8c4c59c6f2b9eae169bf86328adccc477c00b56d3661e3e9fb397b184" +dependencies = [ + "downcast-rs", + "libm", + "memory_units 0.4.0", + "num-rational 0.4.1", + "num-traits", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d8fb2548c..0c560ef97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,11 @@ futures = "0.3" thiserror = "1.0" tokio = { version = "1.20", features = ["macros", "rt-multi-thread", "sync", "signal"] } pin-project-lite = "0.2" +parking_lot = "0.12.1" # subxt subxt = { git = "https://github.com/paritytech/subxt" } +scale-value = "0.5.0" # substrate sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/artifacts/polkadot.scale b/artifacts/polkadot.scale index c225eccfa8060cb4b7c33647632895e4365f9307..1dd7868a3af152f06addd477574c2c4f6b6e9a5f 100644 GIT binary patch delta 1939 zcmai#T}V@57{}j_SY$Bq6&548@!EKZ4g{&hF5XdyWS>=T(OX+d$G^A4 zqZ0e`(}vVWn`dkc00O8Gq@9%4)ESD=(Qz*w2!~85$pT!elc)8p#f?{BxfMZwTX=kU zjA{>g!#>KF7PI>YaShR^K+tl-8%(eAUoitMF!WNiH4>phzCfG+8KQ(`*-$3(Nx!}& z&ivb(OGWZz=GKkHBEiti-X^=Z3DqayBm*%jEl#;+HGkj;u9$2M$4w@O-V@W?!l9AC zsF@^FilsR;j#9hSqH>r4YTji&$xs#Bf>AHZcLw{k2pxr)pMEDpHbdTRhBK&6msc@* z8L9HxXJ>oR+%{Zg%8dA!O6=dv^jt-UScefG*G#1wmuxgt%AH06Jdr{Bgpnk(Is|~+ zSxohg`fN**N<|gG3ww?rfzp?hVQ(j7si!Lg*8-WyR(L13Y6_qCP5`MuSn= zu}%9-1|>o_?fATOh+S*NW_VDVL55>+i?Q@!4fDGVS5@8T=nH#EayT5Pd?d*eA*Wr0=&QBYLWkXW!LrqqU7AT*%R&;Xi`qq9dI_#U0I&pvd%xcGzrE(_b_MCf}9F zc)pbWfRkANk_YfzS<<;AeJ#LMNdH!KA$Ha2-@11J*B;iFGVQoX-=OsE8h*TvUXg?a zYNQtyaFztNYJym%$3j!n24o$z*lan-6vgz^(^Og!>eT|0g@!D2>u7^oz?A$9%Z~d) z%&3KxU!r-k(gls&i~%(%Ks#drA!aA%JmAXT<6O{mKy^s+xxk^@4x7a6g(I|VI7%`t zaFDFEz*)pgTq``n#!E^ZB?&Uy2EEL7j#jeX1&7GXUMT0@Y0Y@t&|IPlob512>S?-y zd~(AnwI?EbvP!S}z<^4f80EF9B)4iA+A;O-2KdQF2UN+<*sG9HJ6r99F)jUt{phdn zNB_4X9Y{=X%b4wZ|3i?J=x@O4FQ280N^pEXi>GAHMe}LJ~29%>; z(F?x4^eZ*7W%N$dh98$V@+MO{Dq#I4DD@6&Se;*~vq`1faYi`}t6Sw*uXLAK4}E2JK@c^pl = OnceCell::new(); -macro_rules! impl_atomic_u32_parameter_types { - ($mod:ident, $name:ident) => { - mod $mod { - use std::sync::atomic::{AtomicU32, Ordering}; - - static VAL: AtomicU32 = AtomicU32::new(0); - - pub struct $name; - - impl $name { - pub fn get() -> u32 { - VAL.load(Ordering::SeqCst) - } - } - - impl> frame_support::traits::Get for $name { - fn get() -> I { - I::from(Self::get()) - } - } - - impl $name { - pub fn set(val: u32) { - VAL.store(val, std::sync::atomic::Ordering::SeqCst); - } - } - } - - pub use $mod::$name; - }; -} - -mod max_weight { - use std::sync::atomic::{AtomicU64, Ordering}; - - static VAL: AtomicU64 = AtomicU64::new(0); - - pub struct MaxWeight; - - impl MaxWeight { - pub fn get() -> u64 { - VAL.load(Ordering::SeqCst) - } - } - - impl> frame_support::traits::Get for MaxWeight { - fn get() -> I { - I::from(Self::get()) - } - } - - impl MaxWeight { - pub fn set(val: u64) { - VAL.store(val, std::sync::atomic::Ordering::SeqCst); - } - } -} - -mod db_weight { - use frame_support::weights::RuntimeDbWeight; - use std::sync::atomic::{AtomicU64, Ordering}; - - static READ: AtomicU64 = AtomicU64::new(0); - static WRITE: AtomicU64 = AtomicU64::new(0); - - pub struct DbWeight; - - impl DbWeight { - pub fn get() -> RuntimeDbWeight { - RuntimeDbWeight { - read: READ.load(Ordering::SeqCst), - write: WRITE.load(Ordering::SeqCst), - } - } - - pub fn set(weight: RuntimeDbWeight) { - READ.store(weight.read, Ordering::SeqCst); - WRITE.store(weight.write, Ordering::SeqCst) - } - } - - impl> frame_support::traits::Get for DbWeight { - fn get() -> I { - I::from(Self::get()) - } - } -} - -use crate::prelude::*; -use frame_support::{traits::ConstU32, weights::Weight, BoundedVec}; - -pub mod static_types { - use super::*; - - impl_atomic_u32_parameter_types!(max_length, MaxLength); - impl_atomic_u32_parameter_types!(max_votes_per_voter, MaxVotesPerVoter); - pub use db_weight::DbWeight; - pub use max_weight::MaxWeight; -} #[cfg(feature = "westend")] pub mod westend { @@ -464,6 +368,7 @@ fn mock_votes(voters: u32, desired_targets: u16) -> Vec<(u32, u16)> { } #[cfg(test)] +#[test] fn mock_votes_works() { assert_eq!(mock_votes(3, 2), vec![(0, 0), (1, 1), (2, 0)]); assert_eq!(mock_votes(3, 3), vec![(0, 0), (1, 1), (2, 2)]); diff --git a/src/helpers.rs b/src/helpers.rs index c43816515..908827614 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,6 +1,6 @@ -use crate::{chain, opt::Solver, prelude::*}; +use crate::{chain, opt::Solver, prelude::*, static_types}; use frame_election_provider_support::{PhragMMS, SequentialPhragmen}; -use frame_support::BoundedVec; +use frame_support::{weights::Weight, BoundedVec}; use pallet_election_provider_multi_phase::{SolutionOf, SolutionOrSnapshotSize}; use pin_project_lite::pin_project; use sp_npos_elections::ElectionScore; @@ -90,7 +90,7 @@ macro_rules! helpers_for_runtime { pub async fn [](api: &SubxtClient, hash: Option) -> Result { use crate::chain::[<$runtime>]::{epm::RoundSnapshot, runtime}; - use crate::chain::static_types; + use crate::static_types; let RoundSnapshot { voters, targets } = api .storage().fetch(&runtime::storage().election_provider_multi_phase().snapshot(), hash) @@ -115,34 +115,6 @@ macro_rules! helpers_for_runtime { Ok((voters, targets, desired_targets)) } - - pub fn [](api: &SubxtClient) { - use chain::static_types; - use sp_runtime::Perbill; - use crate::chain::[<$runtime>]::runtime; - - // maximum weight of the signed submission is exposed from metadata and MUST be this. - let max_weight = api.constants().at(&runtime::constants().election_provider_multi_phase().signed_max_weight()).expect("constant `max weight` must exist").ref_time; - - // allow up to 75% of the block size to be used for signed submission, length-wise. This - // value can be adjusted a bit if needed. - let max_length = Perbill::from_rational(90_u32, 100) * api.constants().at(&runtime::constants().system().block_length()).expect("constant `block length` must exist").max.normal; - - let db_weight = api.constants().at(&runtime::constants().system().db_weight()).expect("constant `DbWeight` must exist"); - - let system_db_weight = - frame_support::weights::RuntimeDbWeight { read: db_weight.read, write: db_weight.write }; - - static_types::DbWeight::set(system_db_weight); - static_types::MaxWeight::set(max_weight); - static_types::MaxLength::set(max_length); - static_types::MaxVotesPerVoter::set(max_length); - - log::trace!(target: LOG_TARGET, "Constant `max_votes_per_voter`: {:?}", static_types::MaxVotesPerVoter::get()); - log::trace!(target: LOG_TARGET, "Constant `db_weight`: {:?}", static_types::DbWeight::get()); - log::trace!(target: LOG_TARGET, "Constant `max_weight`: {:?}", static_types::MaxWeight::get()); - log::trace!(target: LOG_TARGET, "Constant `max_length`: {:?}", static_types::MaxLength::get()); - } } }; } @@ -153,3 +125,43 @@ helpers_for_runtime!(polkadot); helpers_for_runtime!(kusama); #[cfg(feature = "westend")] helpers_for_runtime!(westend); + +pub(crate) async fn read_metadata_constants(api: &SubxtClient) -> Result<(), Error> { + let max_weight = { + let val = api + .constants() + .at(&subxt::dynamic::constant("ElectionProviderMultiPhase", "SignedMaxWeight"))?; + + deserialize_scale_value::(val)? + }; + + let max_length: u32 = { + let val = api + .constants() + .at(&subxt::dynamic::constant("ElectionProviderMultiPhase", "MinerMaxLength")) + .expect("MinerMaxLength"); + + deserialize_scale_value::(val)? + }; + + let max_votes_per_voter: u32 = { + let val = api + .constants() + .at(&subxt::dynamic::constant("ElectionProviderMultiPhase", "MinerMaxVotesPerVoter")) + .expect("MinerMaxVotesPerVoter"); + + deserialize_scale_value::(val)? + }; + + static_types::MaxWeight::set(max_weight); + static_types::MaxLength::set(max_length); + static_types::MaxVotesPerVoter::set(max_votes_per_voter); + + Ok(()) +} + +fn deserialize_scale_value<'a, T: serde::Deserialize<'a>>( + val: scale_value::Value, +) -> Result { + scale_value::serde::from_value::<_, T>(val).map_err(|e| Error::Other(e.to_string())) +} diff --git a/src/lib.rs b/src/lib.rs index 33ad44ed8..e2879b5f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,3 +10,4 @@ pub mod opt; pub mod prelude; pub mod prometheus; pub mod signer; +pub mod static_types; diff --git a/src/main.rs b/src/main.rs index 4e7e6b9cf..595b029bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,7 @@ mod opt; mod prelude; mod prometheus; mod signer; +mod static_types; use clap::Parser; use futures::future::{BoxFuture, FutureExt}; @@ -77,7 +78,7 @@ async fn main() -> Result<(), Error> { chain::SHARED_CLIENT.set(api.clone()).expect("shared client only set once; qed"); let outcome = any_runtime!(chain, { - tls_update_runtime_constants(&api); + helpers::read_metadata_constants(&api).await.unwrap(); // Start a new tokio task to perform the runtime updates in the background. let update_client = api.subscribe_to_updates(); diff --git a/src/opt.rs b/src/opt.rs index 08d9872d2..8f01251ba 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -41,7 +41,6 @@ macro_rules! any_runtime { $crate::monitor::run_polkadot as monitor_cmd, $crate::dry_run::run_polkadot as dry_run_cmd, $crate::emergency_solution::run_polkadot as emergency_cmd, - $crate::helpers::update_runtime_constants_polkadot as tls_update_runtime_constants, $crate::chain::polkadot::runtime }; $($code)* @@ -60,7 +59,6 @@ macro_rules! any_runtime { $crate::monitor::run_kusama as monitor_cmd, $crate::dry_run::run_kusama as dry_run_cmd, $crate::emergency_solution::run_kusama as emergency_cmd, - $crate::helpers::update_runtime_constants_kusama as tls_update_runtime_constants, $crate::chain::kusama::runtime }; $($code)* @@ -79,7 +77,6 @@ macro_rules! any_runtime { $crate::monitor::run_westend as monitor_cmd, $crate::dry_run::run_westend as dry_run_cmd, $crate::emergency_solution::run_westend as emergency_cmd, - $crate::helpers::update_runtime_constants_westend as tls_update_runtime_constants, $crate::chain::westend::runtime }; $($code)* From 451fe22324853412c18ec92a6d3a5402cbd75447 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Sep 2022 15:05:54 +0200 Subject: [PATCH 02/18] update subxt --- Cargo.lock | 8 ++++---- src/chain.rs | 7 ++----- src/error.rs | 4 ++-- src/monitor.rs | 26 +++++--------------------- src/opt.rs | 5 +---- 5 files changed, 14 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0cf356e1..44ea2ebb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3575,7 +3575,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "subxt" version = "0.23.0" -source = "git+https://github.com/paritytech/subxt#a71223ab4a902c38b7785f42dbf2a2457f960dc8" +source = "git+https://github.com/paritytech/subxt#359c3dae414e2503a46f124003c417dded0f51b7" dependencies = [ "bitvec", "derivative", @@ -3601,7 +3601,7 @@ dependencies = [ [[package]] name = "subxt-codegen" version = "0.23.0" -source = "git+https://github.com/paritytech/subxt#a71223ab4a902c38b7785f42dbf2a2457f960dc8" +source = "git+https://github.com/paritytech/subxt#359c3dae414e2503a46f124003c417dded0f51b7" dependencies = [ "darling", "frame-metadata", @@ -3618,7 +3618,7 @@ dependencies = [ [[package]] name = "subxt-macro" version = "0.23.0" -source = "git+https://github.com/paritytech/subxt#a71223ab4a902c38b7785f42dbf2a2457f960dc8" +source = "git+https://github.com/paritytech/subxt#359c3dae414e2503a46f124003c417dded0f51b7" dependencies = [ "darling", "proc-macro-error", @@ -3629,7 +3629,7 @@ dependencies = [ [[package]] name = "subxt-metadata" version = "0.23.0" -source = "git+https://github.com/paritytech/subxt#a71223ab4a902c38b7785f42dbf2a2457f960dc8" +source = "git+https://github.com/paritytech/subxt#359c3dae414e2503a46f124003c417dded0f51b7" dependencies = [ "frame-metadata", "parity-scale-codec", diff --git a/src/chain.rs b/src/chain.rs index 183409c36..5ce743056 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -6,18 +6,16 @@ // polkadot, that only has `const` and `type`s that are used in the runtime, and we can import // that. -use crate::prelude::*; -use crate::static_types; +use crate::{prelude::*, static_types}; use codec::{Decode, Encode}; use frame_support::{traits::ConstU32, weights::Weight, BoundedVec}; -use jsonrpsee::{core::client::ClientT, rpc_params}; use once_cell::sync::OnceCell; use pallet_transaction_payment::RuntimeDispatchInfo; use sp_core::Bytes; +use subxt::rpc::rpc_params; pub static SHARED_CLIENT: OnceCell = OnceCell::new(); - #[cfg(feature = "westend")] pub mod westend { use super::*; @@ -342,7 +340,6 @@ fn get_weight(tx: subxt::tx::StaticTxPayload) -> Weight { let bytes: Bytes = client .rpc() - .client .request( "state_call", rpc_params!["TransactionPaymentCallApi_query_call_info", call_data], diff --git a/src/error.rs b/src/error.rs index 28c39b52b..f54c7f836 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,8 +8,6 @@ pub enum Error { RpcError(#[from] jsonrpsee::core::Error), #[error("subxt error: `{0}`")] Subxt(#[from] subxt::Error), - #[error("Codec error: `{0}`")] - Codec(#[from] codec::Error), #[error("Crypto error: `{0:?}`")] Crypto(sp_core::crypto::SecretStringError), #[error("Incorrect phase")] @@ -20,6 +18,8 @@ pub enum Error { AccountDoesNotExists, #[error("Submission with better score already exist")] BetterScoreExist, + #[error("Invalid chain: `{0}`, staking-miner supports only polkadot, kusama and westend")] + InvalidChain(String), #[error("Other error: `{0}`")] Other(String), } diff --git a/src/monitor.rs b/src/monitor.rs index 705112418..12b2c5ddf 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -8,7 +8,7 @@ use crate::{ use pallet_election_provider_multi_phase::{RawSolution, SolutionOf}; use sp_runtime::Perbill; use std::sync::Arc; -use subxt::{rpc::Subscription, tx::TxStatus, Error as SubxtError}; +use subxt::{rpc::Subscription, tx::TxStatus}; use tokio::sync::Mutex; macro_rules! monitor_cmd_for { @@ -358,28 +358,12 @@ monitor_cmd_for!(kusama); monitor_cmd_for!(westend); fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender, err: Error) { - use jsonrpsee::{core::Error as RpcError, types::error::CallError}; - match err { - Error::Subxt(SubxtError::Rpc(RpcError::Call(CallError::Custom(e)))) => { - const BAD_EXTRINSIC_FORMAT: i32 = 1001; - const VERIFICATION_ERROR: i32 = 1002; - - // Check if the transaction gets fatal errors from `author` RPC. - // It's possible to get other errors such as outdated nonce and similar - // but then it should be possible to try again in the next block or round. - if e.code() == BAD_EXTRINSIC_FORMAT || e.code() == VERIFICATION_ERROR { - let _ = - tx.send(Error::Subxt(SubxtError::Rpc(RpcError::Call(CallError::Custom(e))))); - } - }, - Error::Subxt(SubxtError::Rpc(RpcError::RequestTimeout)) | - Error::Subxt(SubxtError::Rpc(RpcError::Call(CallError::Failed(_)))) => (), - // Regard the rest of subxt errors has fatal (including rpc) - Error::Subxt(e) => { - let _ = tx.send(Error::Subxt(e)); + Error::AlreadySubmitted | Error::BetterScoreExist | Error::IncorrectPhase => {}, + err => { + log::warn!("Got error: {:?}", err); + let _ = tx.send(err); }, - _ => (), } } diff --git a/src/opt.rs b/src/opt.rs index 8f01251ba..a65d90d3a 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -180,10 +180,7 @@ impl std::str::FromStr for Chain { "polkadot" => Ok(Self::Polkadot), "kusama" => Ok(Self::Kusama), "westend" => Ok(Self::Westend), - chain => Err(Error::Other(format!( - "expected chain to be polkadot, kusama or westend; got: {}", - chain - ))), + chain => Err(Error::InvalidChain(chain.to_string())), } } } From 2d070645a4aac7a837d465026452cad273607865 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 11:45:00 +0200 Subject: [PATCH 03/18] use new upgrade stream API --- Cargo.lock | 55 +++++++++++++++++++------------ Cargo.toml | 4 +-- src/helpers.rs | 6 +++- src/main.rs | 88 +++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 115 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44ea2ebb6..7ae6cc55e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1840,9 +1840,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.1.5" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -2360,22 +2360,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] -name = "scale-decode" +name = "scale-bits" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70dece385bc3e5918109830d9509806b5d4525fdf594e3463078c529122979e" +checksum = "8dd7aca73785181cc41f0bbe017263e682b585ca660540ba569133901d013ecf" dependencies = [ - "bitvec", "parity-scale-codec", "scale-info", + "serde", +] + +[[package]] +name = "scale-decode" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d823d4be477fc33321f93d08fb6c2698273d044f01362dc27573a750deb7c233" +dependencies = [ + "parity-scale-codec", + "scale-bits", + "scale-info", "thiserror", ] [[package]] name = "scale-info" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" +checksum = "333af15b02563b8182cd863f925bd31ef8fa86a0e095d30c091956057d436153" dependencies = [ "bitvec", "cfg-if", @@ -2387,9 +2398,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" +checksum = "53f56acbd0743d29ffa08f911ab5397def774ad01bab3786804cf6ee057fb5e1" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2399,14 +2410,14 @@ dependencies = [ [[package]] name = "scale-value" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae8b296b3ebcb3425661e9b612ccc34cb1064483a61dc379c65e6b1463498f1" +checksum = "16a5e7810815bd295da73e4216d1dfbced3c7c7c7054d70fa5f6e4c58123fff4" dependencies = [ - "bitvec", "either", "frame-metadata", "parity-scale-codec", + "scale-bits", "scale-decode", "scale-info", "serde", @@ -3574,8 +3585,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "subxt" -version = "0.23.0" -source = "git+https://github.com/paritytech/subxt#359c3dae414e2503a46f124003c417dded0f51b7" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a8757ee0e19f87e722577282ab1386c86592a4b13ff963b9c6ec4176348104c" dependencies = [ "bitvec", "derivative", @@ -3600,8 +3612,9 @@ dependencies = [ [[package]] name = "subxt-codegen" -version = "0.23.0" -source = "git+https://github.com/paritytech/subxt#359c3dae414e2503a46f124003c417dded0f51b7" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb77f93e11e6ff3ff95fe33e3d24c27c342e16feca8ef47759993614ebe90d2c" dependencies = [ "darling", "frame-metadata", @@ -3617,8 +3630,9 @@ dependencies = [ [[package]] name = "subxt-macro" -version = "0.23.0" -source = "git+https://github.com/paritytech/subxt#359c3dae414e2503a46f124003c417dded0f51b7" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051cc21d77a54ae944b872eafdf9edfe1d9134fdfcfc3477dc10f763c76564a9" dependencies = [ "darling", "proc-macro-error", @@ -3628,8 +3642,9 @@ dependencies = [ [[package]] name = "subxt-metadata" -version = "0.23.0" -source = "git+https://github.com/paritytech/subxt#359c3dae414e2503a46f124003c417dded0f51b7" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ed78d80db3a97d55e8b1cfecc1f6f9e21793a589d4e2e5f4fe2d6d5850c2e54" dependencies = [ "frame-metadata", "parity-scale-codec", diff --git a/Cargo.toml b/Cargo.toml index 0c560ef97..99b87b4e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,8 @@ pin-project-lite = "0.2" parking_lot = "0.12.1" # subxt -subxt = { git = "https://github.com/paritytech/subxt" } -scale-value = "0.5.0" +subxt = "0.24.0" +scale-value = "0.6.0" # substrate sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/src/helpers.rs b/src/helpers.rs index 908827614..15a82298f 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -153,6 +153,10 @@ pub(crate) async fn read_metadata_constants(api: &SubxtClient) -> Result<(), Err deserialize_scale_value::(val)? }; + log::trace!(target: LOG_TARGET, "ElectionProvider::MaxWeight {}", max_weight.ref_time()); + log::trace!(target: LOG_TARGET, "ElectionProvider::MaxLength {}", max_length); + log::trace!(target: LOG_TARGET, "ElectionProvider::MaxVotesPerVoter {}", max_votes_per_voter); + static_types::MaxWeight::set(max_weight); static_types::MaxLength::set(max_length); static_types::MaxVotesPerVoter::set(max_votes_per_voter); @@ -161,7 +165,7 @@ pub(crate) async fn read_metadata_constants(api: &SubxtClient) -> Result<(), Err } fn deserialize_scale_value<'a, T: serde::Deserialize<'a>>( - val: scale_value::Value, + val: subxt::dynamic::DecodedValue, ) -> Result { scale_value::serde::from_value::<_, T>(val).map_err(|e| Error::Other(e.to_string())) } diff --git a/src/main.rs b/src/main.rs index 595b029bd..089208302 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,6 +45,7 @@ use futures::future::{BoxFuture, FutureExt}; use jsonrpsee::ws_client::WsClientBuilder; use opt::Command; use prelude::*; +use tokio::sync::oneshot; use tracing_subscriber::EnvFilter; #[tokio::main] @@ -78,20 +79,13 @@ async fn main() -> Result<(), Error> { chain::SHARED_CLIENT.set(api.clone()).expect("shared client only set once; qed"); let outcome = any_runtime!(chain, { - helpers::read_metadata_constants(&api).await.unwrap(); + helpers::read_metadata_constants(&api).await?; + + let (tx_upgrade, rx_upgrade) = oneshot::channel::(); // Start a new tokio task to perform the runtime updates in the background. - let update_client = api.subscribe_to_updates(); - tokio::spawn(async move { - match update_client.perform_runtime_updates().await { - Ok(()) => { - crate::prometheus::on_runtime_upgrade(); - }, - Err(e) => { - log::error!(target: LOG_TARGET, "Runtime update failed with result: {:?}", e); - }, - } - }); + // if this fails then the miner will be stopped and has to be re-started. + tokio::spawn(runtime_upgrade_task(api.clone(), tx_upgrade)); let fut = match command { Command::Monitor(cfg) => monitor_cmd(api, cfg).boxed(), @@ -99,7 +93,7 @@ async fn main() -> Result<(), Error> { Command::EmergencySolution(cfg) => emergency_cmd(api, cfg).boxed(), }; - run_command(fut).await + run_command(fut, rx_upgrade).await }); log::info!(target: LOG_TARGET, "round of execution finished. outcome = {:?}", outcome); @@ -107,7 +101,10 @@ async fn main() -> Result<(), Error> { } #[cfg(target_family = "unix")] -async fn run_command(fut: BoxFuture<'_, Result<(), Error>>) -> Result<(), Error> { +async fn run_command( + fut: BoxFuture<'_, Result<(), Error>>, + rx_upgrade: oneshot::Receiver, +) -> Result<(), Error> { use tokio::signal::unix::{signal, SignalKind}; let mut stream_int = signal(SignalKind::interrupt()).map_err(Error::Io)?; @@ -120,19 +117,80 @@ async fn run_command(fut: BoxFuture<'_, Result<(), Error>>) -> Result<(), Error> _ = stream_term.recv() => { Ok(()) } + res = rx_upgrade => { + match res { + Ok(err) => Err(err), + Err(_) => unreachable!("A message is sent before the upgrade task is closed; qed"), + } + }, res = fut => res, } } #[cfg(not(unix))] -async fn run_command(fut: BoxFuture<'_, Result<(), Error>>) -> Result<(), E> { +async fn run_command( + fut: BoxFuture<'_, Result<(), Error>>, + rx_upgrade: oneshot::Receiver, +) -> Result<(), Error> { use tokio::signal::ctrl_c; select! { _ = ctrl_c() => {}, + res = rx_upgrade => { + match res { + Ok(err) => Err(err), + Err(_) => unreachable!("A message is sent before the upgrade task is closed; qed"), + } + }, res = fut => res, } } +/// Runs until the RPC connection fails or the incompatible metadata. +async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { + let updater = api.subscribe_to_updates(); + + let mut update_stream = match updater.runtime_updates().await { + Ok(u) => u, + Err(e) => { + let _ = tx.send(e.into()); + return; + }, + }; + + loop { + // if the runtime upgrade subscription fails then try establish a new one and if it fails quit. + let update = match update_stream.next().await { + Some(Ok(update)) => update, + _ => { + log::warn!(target: LOG_TARGET, "Runtime upgrade subscription failed"); + update_stream = match updater.runtime_updates().await { + Ok(u) => u, + Err(e) => { + let _ = tx.send(e.into()); + return; + }, + }; + continue; + }, + }; + + let version = update.runtime_version().spec_version; + match updater.apply_update(update) { + Ok(()) => { + if let Err(e) = helpers::read_metadata_constants(&api).await { + let _ = tx.send(e.into()); + return; + } + prometheus::on_runtime_upgrade(); + log::info!(target: LOG_TARGET, "upgrade to version: {} successful", version); + }, + Err(e) => { + log::warn!(target: LOG_TARGET, "upgrade to version: {} failed: {:?}", version, e); + }, + } + } +} + #[cfg(test)] mod tests { use super::*; From 78ab3caeb75df91fbd805f19d51e87a3e423657e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 12:18:09 +0200 Subject: [PATCH 04/18] remove unused deps --- Cargo.lock | 1 - Cargo.toml | 1 - src/monitor.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ae6cc55e..eddbc3af6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3498,7 +3498,6 @@ dependencies = [ "pallet-election-provider-multi-phase", "pallet-transaction-payment", "parity-scale-codec", - "parking_lot", "paste", "pin-project-lite", "prometheus", diff --git a/Cargo.toml b/Cargo.toml index 99b87b4e3..6f923fb07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ futures = "0.3" thiserror = "1.0" tokio = { version = "1.20", features = ["macros", "rt-multi-thread", "sync", "signal"] } pin-project-lite = "0.2" -parking_lot = "0.12.1" # subxt subxt = "0.24.0" diff --git a/src/monitor.rs b/src/monitor.rs index 12b2c5ddf..501311377 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -361,7 +361,6 @@ fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender match err { Error::AlreadySubmitted | Error::BetterScoreExist | Error::IncorrectPhase => {}, err => { - log::warn!("Got error: {:?}", err); let _ = tx.send(err); }, } From 8c90bb57868471dc2a40d083aeecac904c6b875c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 12:21:17 +0200 Subject: [PATCH 05/18] revert artifacts --- artifacts/kusama.scale | Bin 382387 -> 382896 bytes artifacts/westend.scale | Bin 283312 -> 283842 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/artifacts/kusama.scale b/artifacts/kusama.scale index ab5edc1963b8e92fe17f60517a314ae7874cee0e..31dd5733cc50320c0e2e80c3f38bfac221d0147c 100644 GIT binary patch delta 1809 zcmai!T}V@57{|~1zFQq_=5#DSE_6%H(u_?c?P$aNC^J`*GQBAL*iLS?58a22C`=irU1Zipmqqj*@$AE$xvO!W|8vg%|L6JfaN}7M z!K{h9AVp)bc?NpCW@{j0RULQ=D&&fzo`EiI9mpZAyXDq>2=#&OH0*tpGd zdOPa%)zMJI!A~9xyTC0xSO!)5cRcbV#qb)!@-R7%-sP7$*t`X%aq%Q7 zNQ-u9zzd5oi|?C6g&D)*2DUc_6tqbvqi)lnbx6lBQR5b514U7^NiU-w3A$cF2ShKI zQEEu^atR$$L_HBp&y{X9ArHpn~X(F3S#7aT@6{riYI z!cF+x3aV5;mUKI|g3ct6mR3+*0*M5v;Ja4QzCF@ll4c8A)=+_#tR_jQXKToeRi99P z*fd}G6-`0a6B@*Y`@4v4^hqJ|A_*c>Nf4PA^jJjj+s`OFY)asvTK!UTk_3sfQm7Cf zY9;Cd3Y8LDegXk$qc6-H{Z82S&mYCNMNe)d>lWQgso7 zltekg3>-;DH348WY*5(!v;@^#>qr}S%@^o|*BqB_pxARhoyaM<<%ebI1c2ysj$1ZS z>^;9v?m6GTDGS;`6QA2D@1~7aMw^p$8+-gMth3K+RVpd!Bt?a*9jvX->eXllDxYgQ e06A9eV!z4dWj(E|TljS=mDxn2_?zAGQ@;P5R(eMO delta 1237 zcmZ{kT}V@57{|~1K4AC9%gF2>L@(aKz^oXItJiA%ncL4N+8j38TJFUueMN z?(#}Lm)j-MPak1Ze(@W+fgHi;ghLt=XT7m%UFwV0B;kG75g#tgO2&SS=9p;P>NN=Y zCT6y#zcI~<2%7ShJC%vVEA{<>dYjKXI5K3lSmebfJP621(F!h;O{Z}P7w%g=gKuHA z&u+wZsBqsi6Sh0lKJ)}fPpZMj0&c2OgQF|>(5|fQ1MW?tm-J}_8sta>Iu2h`UnNN$>(Hp`&#l8W({KC&OSmX2 z{`V_Px-=-~_d^-o59O*-$e>Ka#~e3L1mUJMMkb5`bIGURw(4JN&?$Uv`8kHTJ=dke z*Sg1`0+%434V&RRx~cMgl$4X7Y&38^#i Zkeva!Cdn^&kSNEm>6}EK9MGK${ReLro}vH% diff --git a/artifacts/westend.scale b/artifacts/westend.scale index 2de92597b69382873f43d1e74623bcb7abc77166..bb375bd9dd6e7a274a503e75699b216c54bd75cc 100644 GIT binary patch delta 1931 zcmai#-%Aux6vyv5yS8Huy1Hsf{#qn|P>@QLZL9bjEk!YZJs4^0s7tzQv%98Xg-Y}r zN}>CZ5k2%!DJpASNF_D;0|a{zlnCmnAJ9t=_0o(w>$uL2Ps6?Ed++_8b3b#>)QhO4 zmr+y05T4TJ^z^iP9i5~1pdyUV@bp~te}#R>W!3szUT3%9|89ujR5IW%s7CF4`S!^F zmd_hLdTeW9acfr>Ax82Ez}p}=>=iDLSM)i&PPfaxB^fn8Oy?9m%bdk|h!02TEOGlT zbqS>|hr3N^Q^fT1B(AZQc`P$TE&~)aIl7h5X!hfHUOhnz7V)D$hIUv~?uK7oR_FWY z_1Q3PBj1|w@nR^WO>d4y9Er3y&-XdHx!x>Z+TIwr20Af;XJW%m z&@97TEw%}wf*-APV_CGu{8Kj;XwAbLJi!ura6iOpv(6;eIWS6j-lv5k#wl3RMiDau zme`<(x6xcD8&Z{mM*FZaAfkygcq%qg)AytgOSJq=&tQ@E5++%YkMH^K#qq% z!l{EqzE=Gt9*SeTHA^lbkryJQP+@}jep84?PzK`w1Q!;jgQ&QP7T@lg(m6l21ZppX zVbgLiQBfE>sOeq;qf}#wOhsNOmgRSQ1BtX?+Y%2$0Xs4Z3mPzgc!$`)pWKW?m zi#wajf}XRDj#6RR(LJPg5*H}}q3$TRq>jQ077hOnLOHXq8d|u7+$yI_5UX1IDg{@E px6^DUEG2}EeKEvb`41#yj%xq_ delta 1412 zcmZ{kOH31C5XX0>3tePW7t%mUz=EPkV!|VhVl55tSj3PRQn{$bwqPTD(3jMN_#i$G zq6i6l(HM;$B%~4>S&1O8c#xAMdQgcNy?DXs)eC;xQrc~|$8YAFng9G|GRx2Nrk~|a z4+B$t$?oX8;P;K+0TajRdc8eP`#E30<8gca#gQ+Nji8SFQmB~3&#l}uiHDs%zVnF8 zu=T?wTysxUX$ZhVeb46~IMKjxNIPjuv;|e80WbWp&%^ zHUs}>0M{d1VifvKrSYKthq z&{f*wD{XeWdL4a6yrggkiwzc7 z##~KPSxV3atMz(uQsTgk6h$%S8k#B+9W}`gAdXs^B9qhwYm*&Fku)qjXmv8dYL;Z8 zIesII$5c5=Xb=b2>9yIt2EOVAHmBO!J=l<&{ORfp>Vyv-+?BRi0$HqF01y8-gav$& z4=c$$+I@H%bLzw@(PjG)z|+9-jX~5)(vAeNMPjI&!6sFSM4&)w>mfe}L1v{l`Nh|` zk0w~Q1-uR6bt2&IEY2glUkN)J1kDhB)WTDK^8xP5jzTr^OB+xq=pJHu-U$hRM<)J5 znfQ;0j&1(@LKroqT|(O^>5iaolNbo+dX-bsE+0p&g#3^)wP%qw12oaM-LLs&9QX}n z14;ez+E-|aF+ZZZCM`j*H>TPM`PO7@BKG>UX^Uqh%KSiF00D00`({xaqrR9t_Oo+X zmbh5=mFX9YpPa+k$(omIH-5F+6bu2pbGV9PH*7pK@$S~@3JR+$io=Cq5a6rA!U*A? H3iZH$--X98 From 488b327883fb560516fe9ec1bd90a943048ad128 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 12:56:34 +0200 Subject: [PATCH 06/18] cargo fmt --- src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8ffd80625..eeb5132d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,7 +150,7 @@ async fn run_command( } } -/// Runs until the RPC connection fails or the incompatible metadata. +/// Runs until the RPC connection fails or updating the metadata failed. async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { let updater = api.subscribe_to_updates(); @@ -158,7 +158,7 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(u) => u, Err(e) => { let _ = tx.send(e.into()); - return; + return }, }; @@ -172,10 +172,10 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(u) => u, Err(e) => { let _ = tx.send(e.into()); - return; + return }, }; - continue; + continue }, }; @@ -184,7 +184,7 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(()) => { if let Err(e) = helpers::read_metadata_constants(&api).await { let _ = tx.send(e.into()); - return; + return } prometheus::on_runtime_upgrade(); log::info!(target: LOG_TARGET, "upgrade to version: {} successful", version); From 9dfa543123944181eb9701b0e878b6060a4dc06f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 14:14:45 +0200 Subject: [PATCH 07/18] add static_types.rs --- src/static_types.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/static_types.rs diff --git a/src/static_types.rs b/src/static_types.rs new file mode 100644 index 000000000..d4a8bfd52 --- /dev/null +++ b/src/static_types.rs @@ -0,0 +1,62 @@ +macro_rules! impl_atomic_u32_parameter_types { + ($mod:ident, $name:ident) => { + mod $mod { + use std::sync::atomic::{AtomicU32, Ordering}; + + static VAL: AtomicU32 = AtomicU32::new(0); + + pub struct $name; + + impl $name { + pub fn get() -> u32 { + VAL.load(Ordering::SeqCst) + } + } + + impl> frame_support::traits::Get for $name { + fn get() -> I { + I::from(Self::get()) + } + } + + impl $name { + pub fn set(val: u32) { + VAL.store(val, std::sync::atomic::Ordering::SeqCst); + } + } + } + + pub use $mod::$name; + }; +} + +mod max_weight { + use frame_support::weights::Weight; + use std::sync::atomic::{AtomicU64, Ordering}; + + static VAL: AtomicU64 = AtomicU64::new(0); + + pub struct MaxWeight; + + impl MaxWeight { + pub fn get() -> Weight { + Weight::from_ref_time(VAL.load(Ordering::SeqCst)) + } + } + + impl frame_support::traits::Get for MaxWeight { + fn get() -> Weight { + Self::get() + } + } + + impl MaxWeight { + pub fn set(weight: Weight) { + VAL.store(weight.ref_time(), std::sync::atomic::Ordering::SeqCst); + } + } +} + +impl_atomic_u32_parameter_types!(max_length, MaxLength); +impl_atomic_u32_parameter_types!(max_votes, MaxVotesPerVoter); +pub use max_weight::MaxWeight; From c4270d5e05ec4b8ce1c439ac7bc2eb65be22bae7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 16:04:23 +0200 Subject: [PATCH 08/18] wrapper for metadata constants --- src/error.rs | 2 + src/helpers.rs | 89 ++++++++++++++++++++++++++++++--------------- src/static_types.rs | 1 - 3 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/error.rs b/src/error.rs index f54c7f836..c02eff43d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,4 +22,6 @@ pub enum Error { InvalidChain(String), #[error("Other error: `{0}`")] Other(String), + #[error("Invalid metadata: {0}")] + InvalidMetadata(String), } diff --git a/src/helpers.rs b/src/helpers.rs index 97caa26ea..71b93beae 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -125,36 +125,53 @@ helpers_for_runtime!(kusama); #[cfg(feature = "westend")] helpers_for_runtime!(westend); -pub(crate) async fn read_metadata_constants(api: &SubxtClient) -> Result<(), Error> { - let max_weight = { - let val = api - .constants() - .at(&subxt::dynamic::constant("ElectionProviderMultiPhase", "SignedMaxWeight"))?; - - deserialize_scale_value::(val)? - }; - - let max_length: u32 = { - let val = api - .constants() - .at(&subxt::dynamic::constant("ElectionProviderMultiPhase", "MinerMaxLength")) - .expect("MinerMaxLength"); +#[derive(Copy, Clone, Debug)] +struct EpmConstant { + epm: &'static str, + constant: &'static str, +} - deserialize_scale_value::(val)? - }; +impl EpmConstant { + const fn new(constant: &'static str) -> Self { + Self { epm: "ElectionProviderMultiPhase", constant } + } - let max_votes_per_voter: u32 = { - let val = api - .constants() - .at(&subxt::dynamic::constant("ElectionProviderMultiPhase", "MinerMaxVotesPerVoter")) - .expect("MinerMaxVotesPerVoter"); + const fn to_parts(&self) -> (&'static str, &'static str) { + (self.epm, self.constant) + } - deserialize_scale_value::(val)? - }; + fn to_string(&self) -> String { + format!("{}::{}", self.epm, self.constant) + } +} - log::trace!(target: LOG_TARGET, "ElectionProvider::MaxWeight {}", max_weight.ref_time()); - log::trace!(target: LOG_TARGET, "ElectionProvider::MaxLength {}", max_length); - log::trace!(target: LOG_TARGET, "ElectionProvider::MaxVotesPerVoter {}", max_votes_per_voter); +pub(crate) async fn read_metadata_constants(api: &SubxtClient) -> Result<(), Error> { + const SIGNED_MAX_WEIGHT: EpmConstant = EpmConstant::new("SignedMaxWeight"); + const MAX_LENGTH: EpmConstant = EpmConstant::new("MinerMaxLength"); + const MAX_VOTES_PER_VOTER: EpmConstant = EpmConstant::new("MinerMaxVotesPerVoter"); + + let max_weight = read_constant::(api, SIGNED_MAX_WEIGHT)?; + let max_length: u32 = read_constant(api, MAX_LENGTH)?; + let max_votes_per_voter: u32 = read_constant(api, MAX_VOTES_PER_VOTER)?; + + log::trace!( + target: LOG_TARGET, + "updating metadata constant `{}`: {}", + SIGNED_MAX_WEIGHT.to_string(), + max_weight.ref_time() + ); + log::trace!( + target: LOG_TARGET, + "updating metadata constant `{}`: {}", + MAX_LENGTH.to_string(), + max_length + ); + log::trace!( + target: LOG_TARGET, + "updating metadata constant `{}`: {}", + MAX_VOTES_PER_VOTER.to_string(), + max_votes_per_voter + ); static_types::MaxWeight::set(max_weight); static_types::MaxLength::set(max_length); @@ -163,8 +180,22 @@ pub(crate) async fn read_metadata_constants(api: &SubxtClient) -> Result<(), Err Ok(()) } -fn deserialize_scale_value<'a, T: serde::Deserialize<'a>>( - val: subxt::dynamic::DecodedValue, +fn invalid_metadata_error(item: String, err: E) -> Error { + Error::InvalidMetadata(format!("{} failed: {}", item, err)) +} + +fn read_constant<'a, T: serde::Deserialize<'a>>( + api: &SubxtClient, + constant: EpmConstant, ) -> Result { - scale_value::serde::from_value::<_, T>(val).map_err(|e| Error::Other(e.to_string())) + let (epm_name, constant) = constant.to_parts(); + + let val = api + .constants() + .at(&subxt::dynamic::constant(epm_name, constant)) + .map_err(|e| invalid_metadata_error(constant.to_string(), e))?; + + scale_value::serde::from_value::<_, T>(val).map_err(|e| { + Error::InvalidMetadata(format!("Decoding `{}` failed {}", std::any::type_name::(), e)) + }) } diff --git a/src/static_types.rs b/src/static_types.rs index d4a8bfd52..8f4f8b45f 100644 --- a/src/static_types.rs +++ b/src/static_types.rs @@ -35,7 +35,6 @@ mod max_weight { use std::sync::atomic::{AtomicU64, Ordering}; static VAL: AtomicU64 = AtomicU64::new(0); - pub struct MaxWeight; impl MaxWeight { From 065e803dfd215a1226870ba238de93dca47f4ded Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 17:17:17 +0200 Subject: [PATCH 09/18] add a two new error variants --- src/error.rs | 4 ++++ src/monitor.rs | 10 +++++++--- src/prelude.rs | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/error.rs b/src/error.rs index c02eff43d..fc140d9ad 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,4 +24,8 @@ pub enum Error { Other(String), #[error("Invalid metadata: {0}")] InvalidMetadata(String), + #[error("Transaction rejected: {0}")] + TransactionRejected(String), + #[error("Subscription closed")] + SubscriptionClosed } diff --git a/src/monitor.rs b/src/monitor.rs index 3f764e0d5..500f7df46 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -343,7 +343,7 @@ macro_rules! monitor_cmd_for { return Err(err.into()); }, None => { - return Err(Error::Other(format!("Submit solution failed; watch_extrinsic at {:?} closed", hash))); + return Err(Error::SubscriptionClosed); }, }; @@ -370,7 +370,7 @@ macro_rules! monitor_cmd_for { return Ok(()); } _ => { - return Err(Error::Other(format!("Stopping listen due to other status {:?}", status))); + return Err(Error::TransactionRejected(format!("{:?}", status))); }, } } @@ -389,7 +389,11 @@ monitor_cmd_for!(westend); fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender, err: Error) { match err { - Error::AlreadySubmitted | Error::BetterScoreExist | Error::IncorrectPhase => {}, + Error::AlreadySubmitted + | Error::BetterScoreExist + | Error::IncorrectPhase + | Error::TransactionRejected(_) + | Error::SubscriptionClosed => {}, err => { let _ = tx.send(err); }, diff --git a/src/prelude.rs b/src/prelude.rs index 63fcf5aa0..f56e91fbf 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -59,4 +59,7 @@ pub type Accuracy = sp_runtime::Perbill; pub use subxt::tx::PolkadotExtrinsicParamsBuilder as ExtrinsicParams; /// Subxt client used by the staking miner on all chains. -pub type SubxtClient = subxt::OnlineClient; +pub type SubxtClient = subxt::OnlineClient; + +/// Config used by the staking-miner +pub type Config = subxt::PolkadotConfig; From a3cdc636547eaa2d2e1229add03723b344dade21 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 17:18:20 +0200 Subject: [PATCH 10/18] cargo fmt --- src/error.rs | 2 +- src/monitor.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/error.rs b/src/error.rs index fc140d9ad..ae61c71d5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,5 +27,5 @@ pub enum Error { #[error("Transaction rejected: {0}")] TransactionRejected(String), #[error("Subscription closed")] - SubscriptionClosed + SubscriptionClosed, } diff --git a/src/monitor.rs b/src/monitor.rs index 500f7df46..d1b60eb16 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -389,11 +389,11 @@ monitor_cmd_for!(westend); fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender, err: Error) { match err { - Error::AlreadySubmitted - | Error::BetterScoreExist - | Error::IncorrectPhase - | Error::TransactionRejected(_) - | Error::SubscriptionClosed => {}, + Error::AlreadySubmitted | + Error::BetterScoreExist | + Error::IncorrectPhase | + Error::TransactionRejected(_) | + Error::SubscriptionClosed => {}, err => { let _ = tx.send(err); }, From 85f0937f37b096d8ef6c6525d28a5792d045b230 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Sep 2022 17:52:23 +0200 Subject: [PATCH 11/18] fix test build --- tests/monitor.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/monitor.rs b/tests/monitor.rs index 831f735d8..0e5f2b104 100644 --- a/tests/monitor.rs +++ b/tests/monitor.rs @@ -17,10 +17,7 @@ use std::{ process, time::{Duration, Instant}, }; -use subxt::{ - ext::sp_core::Bytes, - rpc::{rpc_params, SubscriptionClientT}, -}; +use subxt::{ext::sp_core::Bytes, rpc::rpc_params}; const MAX_DURATION_FOR_SUBMIT_SOLUTION: Duration = Duration::from_secs(60 * 15); @@ -50,8 +47,6 @@ async fn test_submit_solution(chain: Chain) { let now = Instant::now(); - let mut success = false; - let key = Bytes( runtime::storage() .election_provider_multi_phase() @@ -61,7 +56,6 @@ async fn test_submit_solution(chain: Chain) { let mut sub = api .rpc() - .client .subscribe("state_subscribeStorage", rpc_params![vec![key]], "state_unsubscribeStorage") .await .unwrap(); From f4697a0a5af654ab3c56f8410b729b48addeb7db Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 29 Sep 2022 16:46:25 +0200 Subject: [PATCH 12/18] kill macros --- artifacts/kusama.scale | Bin 382896 -> 0 bytes artifacts/{polkadot.scale => metadata.scale} | Bin artifacts/westend.scale | Bin 283842 -> 0 bytes src/chain.rs | 178 +---- src/dry_run.rs | 121 ++- src/emergency_solution.rs | 32 +- src/helpers.rs | 183 +++-- src/main.rs | 34 +- src/monitor.rs | 730 ++++++++++--------- src/opt.rs | 63 +- src/prelude.rs | 25 + 11 files changed, 616 insertions(+), 750 deletions(-) delete mode 100644 artifacts/kusama.scale rename artifacts/{polkadot.scale => metadata.scale} (100%) delete mode 100644 artifacts/westend.scale diff --git a/artifacts/kusama.scale b/artifacts/kusama.scale deleted file mode 100644 index 31dd5733cc50320c0e2e80c3f38bfac221d0147c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 382896 zcmeFa3ut87c`mwE)$X2ZyT-0bH0Nl}(cH65+d^LRK=O-u-fSM!s$lu z=&;|dtTYW!_UZ>=H9Cs= z;X(XuXSZ9J+UPXHySn5auGaf?J*B4fRHxf%ge7enC_UTgcA~*S*o#WqzZ&kI=kR!Z z*z4YIHHRNpqQUNNtI-NO{cQVE69Y`?^l(a*FwGNcs<3=mZ@0sKb*J90WAG^5%9}Z7 zp6Y2;(xu&A7*1>VOliLtMq%%E*i1jH9JHcFb+_03MA%6`FYkuo=wmgFX*{N8iVGH6 zgTupaukSZo(P6#c*gv}&_Ezoh^X+;RdEU16RZ&lOy1j#X+t=Q<(v|LE*sJ$j-Hv-Q zdr(X9Emdi&Ywm*okzkBix@_FD(xtS%0oUr=fmGdrou z08p=W(CW8tht>YkVHlOxpMCCmZg^(@D@x@GYQDr_de|j&G_x0W!d|N}-E8fJQGa?> z{^3}qc6TpY(974u#pqwsfgdTNtD z0ECbY*R{8%^mM;_*lI+-m($Z+^fo}cURb;}i0TJ*w=MH-R{TuquS{>r3H8)PeV9J7 zOG`3?W!oHGsTmyH7S4#L4)tAoZd&HiNb9`v?cqW5zW(IUF&k;`srBt%y%W_N#N*BS z5y9oDPwKDOFZ;>c>Y;f((#Z$Z%vQgCtJT@_)TiPpM&{8->yygAvb!7d)O+f4`V~7+ z*FLlD6^`dt7)6+sr@o-KZL7#Ua*YaV=A)Qk^RVuzKaLyinMbbCdz8O%aHZ1*YI^D$ z`ct;6)etjX=yU4;nDm6!uQcTMNUL90GuQg9cB_wGpVl#& z49qjz96Z6_Xaf7C*$0xr!SdwcNViV`L%U#n?Dm5?!F%&;r1^hSV5W`k?U0L`OZxBG zha(^ScLf?6_2pQdPWr!Xo{co0QL|f({jfP`lO&$eG1^A%>BuKftEtUi_b%qMqV=K0 z0kHYIM!7{|%)OR;aA+TneBgt0QPOCU zF1(t2uwx&NeDE7;;rxE9-Aul^srBdF$>N*p#Ba;0wB0(yKy|I#_LDvyj0`fX_T51N zs6_q|I0zf?RH*e2hR#ma$w^g#4Nejm#z?;(RHcu$+90(bPbRR32O|ytZ#8?p+Yfse zTM=3hl5O3o??u%%o{oI-n`-%T_n_4g*<`cZZDXBBT7SV#v=hIp9`fbL_j77tv)-$3 zbQdH)_ZL7mx2g!lYKlK8FbmqH=@Wk=o5YZTI<(SgHx{Ki;*v1RI^vZJ;1P6 z$C>#%J62TfxrZZPTu@V6?QS1q{5>Wg@#jdJk1Bs~4;V=z385-2^___odv0v6RnAJLdsT1Dem5$%-oR$Lz zC4n9I`}M;^2=~pvJCt4Yce_0w60mP%Nh?$Yo0HUt7frUoVcCtUB z{wR&UkD@A+saJFfQevdk_M}nbt+k&Pr!R}r1WC4XCDE9aR!<6pT52sY`b0 zshX~spXU3#TBXWD9hiI9>VOAB^{QO%Ivx2|{e!UIYItg2FDHMR76rAClEdo|BilMHnb+H<-6^GN>4j&etqOVyia~g{ zpbLXV>L@&1%ldUO7+hL;rF!Yg>a~k&)wQcvuUxIJT|R$hb#1k}wej0)uq8Cp{ibKH zXJ#&UFAO?OLf?P6J7_moTTp@2{JF5*y>kiRIT#!OoEFyS<>08in!nb$)#={ptVB`R z2mGclh<*;xRjvWLqH{wlJE_!udh8M4=;G`(9-Cc^#wlEZ3?;v{T3l`2hD_4!oICpM zu-7FXwbOl#gP|_WU!-n*H3UDy_|UK}cQ4kDcrKMXEG}BQYIUqp!dYB^Mi$=1vRZpM zjs;!3vk#oi(iFQDCB2*?HT(=zw7(M53E3~9H#m3FT~LAcq0wUt? zgki_0$)pJq=$8i_*X>+TvHj9U;xJ{*^v13x=e={xXzbDNcl}mB@*9I*4+3+%z>U?w z`;vaosZ*!6L8)F7wfSawzu$+j^~^KPZX*h!!45faKMY#kXL#IB^YJq~1Bh_X$OqAB zLFMR~IX@I_sL2j@jzIldk)wNj)GsdE-P^I2rOfsf!1ufjrOPvGoo4qA_ihqT(BPi1 z9ocTCah{i!&Y3>3gWnn=^_E%qcW}!6sCy8~9{Bd_z+W9ipg~dpnDM6T2b6TPBk96N z^w3aX22diJK5#Ay-3Wp~u;ZQn1Wtj!Gw1^ma9aGeyAW=uztr2S;bAvw^(%K_Kcmpz z+4EcAS70*(NCB`=`R#5L1<=4?57-TRMVj8a2P1f0=#S$1*tzGti>@ytAB0WtuCfK< zR@g6&MKx#fO9MP@^h-Y%#%+NtAONB9ffl`Pw@fVs0x1hu!* zbYvp<(FN!$ZNJ{z1J?!l09}Oj12VfbPE9D@8Jb0d8H?2q0`SMYAISpDc@wm0nzMmZ zRStu_zz9}}<;#cQ6U_zw%Q)T5Uj5F4j8^cTHt5}f%z!Ykaa=$)Ra?+AD_gc`7LU>_jtydSY!l~oqw@4#Ojeu+9LGha2 z1`Ob9p%wye1x3gTIw1ES762I%b++0l^d^N63~nx#Z{8tiB*_YCF$$NP2eIw!C6(q40L40+B+c& zw00Tjbn*Jkz`z41>X;#dlh82M$Q84MrM`lZ~>phSm*?(&GFeR9-AmTLY1IR$FJ}eQ?N?wPehlgn2r8R@9 zH{fJE24>f8+*|}3y9JxI81*3hd)}vw>QBi5TTL{^YvBrF4M#82=fJ7%Yb#Hbevp#Z zWEpcd}8{&-KzWb*l$_i zX@`M-x!cE&qsj*~0CNY%3*W-Uiq68_>~XcIjrFp79Ec<7JtjT^j=;axG2`6_HlOr< zGz5Zy{rDb0n04H>b04ETMxvTnTL@|RN?@h|3AD_$>c*`>$Zhg)ir8$P63H6-gU&6i z9OwzcYYBPnYcW?GblTkpy;}aq2jKvK9XWKXor9@h-N(@gFen^Jmp~40_(<2*pf*e zG4&p#(?XW|4^QE}-jhNL1@C&%Z^rmZoZk!kGCa8*0W_8vTTrcDcf=oFh}{U<#+Y6D zv|h|MQ$_F1mDG}mS0oHrYpVyk5<)K$&*h`!v7ass@z~)>!|qdtDHg&JEg_%LGYDVU z!5%9;vqKaPn{VjS4ov+@eMYI1udCOK3kVpf$0n&V%yLzbsLoy#e+zp}2{8!_I(Y*z z2}*q~R;`_@HpAqV_GYYKx1dWnHqAG6>0|guX`gWkG?iEM;+4ZXPSZ9X(2TBB@g?pz zHkb4fT)=pNEdv1JSrx)Cof^BX;qVwcs9%?z@oO0xFUS2kU1r zknFUE!9BoJ8c~e~BGJ(cl z5RC9U1A11~bqvpqw^U z&ileWzJLPEmX8vOUq(2~SJYRq_`=s_)z^v_al&`0Q^TKKwSX;R?bDls9bh-wsjuth zu=(tB&z*T;X~{kPdK`Z?W8bAd@Y1no-zc7ShEf+vk=Z!WtBR}ZA?W@NTq$sVhY%fM z)0~fYrrK(LOE1zu>LAbHA@4gzB-?kNiZlj4xqNSUWi9nZEtpwpw?EqLax;IhVu%79 zEsl8DlfW|&6BtU8LNgIs(F`gy2!MJ?jEJ5A72>b&=r-edOjH1fh!{k+K^#ITdoKbF z=JYXx;aIj2%K)h|BWrWuT^RaAw}Y^1$9j~g@~zUy&do$UL4*o0q-uYsHvZds**;7# z<=Y9SjDC>8l@x*Y|SVb;U(UVGGq;;D^)d2bkaw)ena!w!Sga z&7>(hlT5M#xL+X64Z_qU)sNyM6NeIw9+^$VO>_Ye$Vz$MGdcy++teIj@OY^4blFa%+lzjpS0w~4N8I={xi;UOGS4~(tZX@O)ScD@v?16@v`QgNU4)Q7d&+?FZo&aaKuYlob zB5dke##dU{EnhX_8@jg;obB&}Qtw0c{Hb1Gl*P9D8!q7+E9v6A7*-hy-cN>0A9$r>Ez&mQ{LM{{l`Fo`U9N+ zM$!Sy5@t{yx;QR4Ks;!_%Y!x6L%87ZqYCUwyZ`kROA~zBQ#!%Co6t2J zo#W=i?j_1FhLwkr)wyPm9w#?|sbS_b?>+C)Bbl&%_rXxyaXD?WE(1F(+ z27%Hsz;-K z<8^k^vNXe42*^nt{<>F7;OOr`{WNs*(W*s1foP}HPe%o>MS>7U4=Vr+v436M+!|nq zFV%mCTIbP<;oDH~ho0{-AQC)DEpD|K4Cg-JsBe4IhV>!rK)-`*f*ZO}xN^%=@1P%5 zL`09LZomqs_`p-@XQQ(e+;GV;#EevOj4t?)bC7U?R0S;!|5=yVhIStlsgO#AQ3zaw z;)@7`B0^nKFLbd}KT7|t)IU#{i0K_uEnxotT3ogIUlQk@k!D@6#Y`;FmL^AAj7Xkf zVMe;Mvf9M+suL-$gfK3NJvS*!s;~j_VI|?RF(HxDrhFTQN|Q0b!(u%fMmm@?u^e?U zGm9d=>0-H-8lyI6&;HcprA?l>kc-$jr;A6(^T{qj7dqXMM&OBFONRxGDGX=|x-+zP-=2%(=xL7XyxjzH+_VU6 zsPo4MEfV(_&_M-~qic2yrOrdyPxlN7Mhr`JZ~b>L>cs(qFtvxB?qF}9Dh8OJ#0`cL`YLAj-k@_$Q`T^+a_+F?L&Um; ziJm9zps2uA2+t(k<}f1BnCl-Pj+Y@O;oUl7vcl$?HaQsduMc{(+6MlLU$by2-+1lD z%{d4G*|`d9PR*3M5oQNV6{9S!X%}!~;?$AMM=Tu>&SR1pbtsNZpj|l!6_&#FX z&T+;dX)XIPlp@$TuS= zzCX0~z~2h#q@+P8YV=x%VwQ$i*R`t!3u_kFpb#M!DCfPs(ZS9$98|<4Y>V~xa^>i( zcLZsN20bWfnAJhNt--tzs7zRfr>6}@D*fjDp`ywcRw&#VDax39VFa~?2D@a~ixF$& z*V}iH2SZ%gMVc>T$ti8yu!wkZkYVOi8x_o6C8EM8OZ#!WPxn7T&wG=uD!P<7J%HWx zX7R`N*f3E{_0ss|vC_C`Hc2~xu*d6Qc z^r0~Ob=W6>t5|`Bb+=c>A;md64be`$O|zvzXNHBDHby@&ts*otflBQtjqkxKY6tY! z@ln`|*InUQRa*IJ4!K`cn6&bf-g8*FOy6`lVeNpzPRLaQLmQza#yLbX9!#DIc&{I& zAgf_&$eqv%|IrsWjil{Zw6#D+E_a5|J0*VFYSt?ZOU2heC1o@Uv)w|`?fCY?Se7~R znV?rt7(}NA3zy_{T{YoY@D05)wkLb$j_=FrPtn#LQV2Tcf3$t48{>$D-X;&0HG`1Z@Bec;wirKD{V777?H zzzbSk{E447<`EGfAk0i6FmaQJ5->5jWBi;$lhjjnHXS$qqA_eoM_8=@%_lw2$~ z2XM@*)=M|WuRA>cj3uUHW{~&3KBQ1P@85^*;0Eta)NU|!4*rckB(xp=Ta^gXF}-@; zXJq=o&+Y)h&=P2xtU^A<3drh!iP)>2{h4G)jqKG4@3eNiE9?sKC@m&1Fc2FO6aFI+ zw9G=J7xs$4S3!2Vz<~I75tPIoj&nVb6eq&im{pvdunBQ|SFj)xTCr}}`@n>l0gfyW zAVoGVYai8nN5k%_$Gk7txMG8y6FtF?@;pDGOE(U??OXL`7pKfr@IkGqgM)5o+SJa- z9B&cwy&2^3B3uu)ps~Kfvmy4d7Y+`p2pWb3EP7Jw4qG*n^Vi8C@j z&U3^=w0dPbdX#{9QWx(5n2{yH3jUO~@lemsBY*ZOFxC7N4C4h98Hq#MY>|-+T$y@* zj&-I)&g~5j;mb)9G9~8H=I)Lp%B#XPEuWH+AQE26Rl!HgQ&Tbv zEwg76OAXp0&qe_6iI@4^3}?oQbMR470FjIn&CDlIO#t#W_Jnek+pkCKJNrPw{#_BA z3?2)8C|6w7oUIUSW*m)B-p@b_>p0h33uPmT_2G}QNqZ8GZA3mYZU1*D2rv&2S$8)6 zu77kMz6Wgg|%>Q+XGVI9HNm%-r_pcykgPqgvpd}-k z$R$61K?#>=gt^cMKWOY1 zA9|wjdEO1BcZPGb_wB% zM*(omGA%Ed3udl$WMx_VXLjNzNDU|V==cg|42zP1KA9V&3Ji9DM18P9l-wc*onal- zG5Q%YrksIve9-bqNZKp@9*o_G1WnS~7#~Ephh1NWG5fwlNmrsGWkk1QM)DSo75PK^ z;i0r6m?~Zpog{#d-Ozzmid~-!TNc( z^mw%6cBA0kP47l*7LUhSHgipN78~BP`+CZ2J9>~zD(aO%iJF!_6jboSUcMU|LAQX{}Zu9UScP51!ui+tb%q<)cV2Xkn z$+m=jluNQ?eKIF_pSNwRR}uvW?t}ZWvb^7WD`n~uYg24s0baWTABI!KNd}Lw3ea4l zxVY+?8HOo}%QW%yM@}OZZpuF!>7&NVEx)g$$hB%8Yk^X?3I{9GLb-&R; z+M0=LpGW9RzdB(QN~KX_fK<#$4Qx2)uhA`4`S-LUj~)*Yz|l1A)I$)-_)KCT!f%WqrZ>55R-sC7o4@L zj4fD`$&Ch~?;yY3k^xY919jN1=wG0b&8+ZI>L6!y926WE6dv8oQe1-=C|_bxiP0pM zxTc-SF84NF*N{mrAgKf2q)Fz5omM6$w<3TKOnqkLMFDbvbW486`C~Jn-$dsXJ+Gi|=*@#xnU4 ztt+P2H3^r{=q$*}ZN-qKj7p)5_fI5W@ByS*~ zhSp%hL=HHJHwx(;p}tLI1p}mAB6Z9>8Ws)48ayQ@;_i{$+_0ua1()e=qf&sU>i_@2>zme@36b(I#>TFs{ zrGnWN6hxH}6d)v0V^AhySVorCBaacU(r(*SwFM&gI*`j0zRGz&1;t{eRdy@|+-T}G zQZub62gm~RL22<7=`4Z0J3!`D7~nDqY@}Gy0B4&;$CAK9lX9;@Kyc_4Gf*Z%dkS6F zk4C9b-dj)OMO}=0NK->kmOnny>3Aq{?Di9ov)uJj68JSed}jr-gYF&~r1gs{og*An z)WGd*+X|65s^yQ;SKA@2M#hV~1_HHTS2t|g{8)p9!>nRRv~-J-A~Uv1HY&inO8U6= zL2ZBnoM4gpIQYO7nIEMCr3&>Wma|0hR49HXYha8aMHL51h0{`?(iBRE>NX; z$lP~HTBkjaOa?yd#x@@1I&wCTnZqm#p&WLhBE5uC-z#jC~&RBI68WT|zOTX&5F^3kG>dsW+%+Ane2y1-h0T zPnVTBdOV9$jO2r6JzU^ivEsV>F5z#GQXqsNNYZ+uMnBXvMvZ=GO@jCs;R@m=9Q`y$ z>(kJu$Z$8!A0?%IH36Nwpdtf0XV0Hml&dX{Krf{pL{bg9VqR>Jzk^b3f(RmZ7-pa& z@(?MC5ZI5Cpd+_`nU7f*sxvmwiR#}_pCUmW8*Q|1CUy>$`3}MrNgSIn>l|t*kMs{c zijha4Q}lANz8^FmCNbR(g9X#Ijp0h-C9=yoE+!{WccEPpMv@RO48_U-7%L9taU;cX zFXI7+En{7?aej~ECgG84IC1~Y_m)&;a6>U6Szez` zU}ogLBr~`p&xWD!zl9Ojb+KX((6(m*!BUAruSo&J|0` z6xu=Y4^~+$qX*_tLi!lrkrc%l(+M;(T4SLi!C=b>b0LK|mozP+w*Z`5dZxv8+ziKS zq%nSsgfZq=0d>qo#A9Ubne!O)YG@-k#;+%j8U+)(LH?_a{vcdzWd4NdPa{fnT;YNw zbYf%)5_d9b2`<3q5z1SkF5#Uta!r{(FvXIX@DDscu&Tpz7X*Ro77NG${2KO zEL({LDrBDbdjsC>!#cHfigBjwPCO9p4Xz53GdSQH02JyMw=jC3&a>)3DCOo`pSUN4 zry}0PdZ2ZjrL@Hd2!?!0)9eNlrR2#aGOQ`Ohn|NGOBUc_^pQg?U8BgA?ZQ|IRjzmu zwv@PEhHQeKD2+6&KC~ee2?yx}xcrDkzhSsTn53~nTTForCEqwhi`^c^pWB+$DM#0R zr^C3I>nwlCdmq%a{MdMz*q#^{uC^9Q1UeWbV6=`cREjl#o*^=oMG65gluC?b zyPTh7>yMeAB?^KZG9y!27J#+&cSfg+C?-K;q-I2+EieYbO0wasQgp)m4spV+^pFi1 zI}n=|aW4g=TR|0+sTG6kCT_yHSGOm<@3UKXj{N)T_z{x3mM+{Exf`WlQ!`gO@cJWA z4u#+icwtmRxoHCvn3Vhrprbpe+XI^t(iTHh0P8f4SS{s17gkZ@vIA`=$yOaYr6?V^ zAhq*RK@SkWg}NuaoJ{1~bM`V9J&RhMli#k!Z>!PA2Mj{(rzmD9_N%I~T?yQ$lG6=c0z`60$ z)T_`s;Hu}v(G!~FQIL<3Sy0L_D}pg$Fi4caj2@+WHb{Jb@vV~?6nC<70XzmR5WzD6 z@8n9MS?$B^X89-Yup(nahx9=jK_kkzZg zXw#!W__m)3ahCU_)RKJSn3;`ZvC)auadPM)SHr_ zT(`4HZaSEHkRg+DTiG%7wAlBf5qK0)x;}jiO1|=D1|aV0f;c`I@{>RWp4iiV%?2ch zTuk9Ye3{W940#l&T)2XjJZ!0N{ADHsj7RFVlhm$hAJos9TWOL2HH=4>YKd`OaobD>e?;59T#*Jq?enQ}^5cA0+4C{dQ%VJ6xG+C~&nD4;J38|kn zB91l70>Dg=pb(h7jVS=$@%VBd0xBWMg*ybF1DIr?q}Cr?(;LQDS>Hj(QA30|ksQ&t z#BxHulfX{gBEfq$;J$9`Gx}7h3Urk5UtQAo1cCLDW>;bc!35ik*Tpv4$q8vfqTTpi z43djA9_TDTC{laV^wuP$6R2%wFS&}5_yd~qtRbukC}KQG(oWM`4K!t`&J0EbxF{NR z_Ykdv(BH0KyJoK%shLYuDx@Xc;Ys`Fi)6(v1xE${jaXQA$Z&>*X>mPa_ekREiBTB} zdO|!I84AkV%A`Fw6fUU>A>lHpmBGlvUu(mk*y?vRsypMoe4>!aN*F#$ol&+b}J3puV7XT(ZY>>>F&w zxs>eTIc9l5n@U=nyH4hUd884Ck!8RPLcnc(zo+%4jk#etc%x5lG!b~SATX%*yL-s= z>rHMq8(%&$u}uLn-GnN#jFB^$hAPs@1qwD&jEF$Iu_y|2pL{ZW=bQ##7*$hXF2inFN$aD6#*6E?$griFqf`@ert4xcw|* z0qsUwr0~3TUFN+#t}0Tu_lH`)5;p@XOR*$e-@FbQcowET^d=dx)+13OPAT<=6USsc zq(PpfGc)Wg$UvPJ*)>0_bpcv{o`(WlKf*sJ^>{|S8{UBEv%0vRYzr0+65Z+`Xe{0!QH0{_jy|tT z6Si^Q!K8|KntNQ2@kjU9%WSex7`48LCBzqPq~3f{k6gBq{qRemEON{D^9v|N@Ffto z{8Jmo+QnspE8r6?LAyz~r9zaeCn^lj2{NRz$fnyEjV*gi`a;k%`gs<&dfH)iu^Lq!J#xudUip#CNXYg#`ExUa<(Qw zCk=r-X)Ilw*_ldgju^~G`=Twc+j)uC0Y74bKh+HX%k}-o#g&%-K zcgM^Hi@bgTUTchHT}VzGOlVJ=kjL!!kC32RGSjnerjTXDBFeKki9p3uJz01{WGpk) zWF)i4^l`;jO+<`PMCB}@X`YsuQs-q-TCjcmoZ_IM`+om{4tWu3*t|~m+3gJVr3>E}?F$|s_Z2ceh7s0A50QVD{zp;|#mnAlE?e0D8L8k{LKVjSobX?A zo3CJ(%qrJ9hOCt`6Um~_EYeKk53JOm0PV!I7E)lLW@@8})XjwL5K^}x#&lV_+pds? zn8wFIQQsB+y}?gN&NT2ru0u6B7*|aBAmDGkgS)bjS2SRV2`^Oa-EK9)DucVt__eq> z6QkZV?ReI48It%f{aVu4)Fr6}3O6QOK(*iwA8x=KljDs{!EVcp4o-4Bgie+=wAudn z0E+d3(@MrNx3^lrnWHj{?$EWde2bq`yeKA6hnRZOiCIy_Tq1Mh<|8Cpf1yjoyNv@V z5%wnW2oq)}s+*+ZU6ojBvV3oE#stBWtM{`k0j*Gelbz4*9*me)ZBO#4_n6@mvH?>l&{5X5bj93T`TH z0Xr);8aixNkuhIw)DP}EOL&CD^RMouybf(7lOGIB z07bEk;HEE&O!kTT>*KLs_$0#WvWllU0cTgia`7&y2%!E3$OJSqh8kn9_*+E!*@liD zK`T9_S6tVL>J;lQ6g7cR)>sJDYQGYzsC~7Pu9@o;Jy^$EPm8OI^3J|utaOI%x`v5`j2vCF)EY)VjA};DT1yIvm zYu+?P;{jqHF;&sDcCfj(+&$PvX*9_@40VCvXGem}2chT}iLZ?!l0;-vpWq+xA@qI8 z#x>WU`=)2>KLZ77bslBJ$=~P#JRP=DDFZGMKkLkO!{;SstomDBJkFZ$a3^xe7N0L& znBwm?#2a7&ZYx5Br&2$@&+bN!%aqt5(bnv||FFu*n2!|#cSLj(FXd?G<6>)a>Jd`%zn=`e zDc&Vb!|#*Xx6*nUsW7el!`9tv#FORk6AH@ou;qaYw4ypC3ZkAP@n=D zOv{G|1RMv-Y58(w8xX54!&ar6af8P#M5!FxViCzX*mw|4irFJ&F!a3KlRP1&OczVa&y z_zBRPdI!P`=jcjtxT$7{8+t@1D3vAGQ&8iyp48y$Tabd8`enTEG^lWnvaI1QA0<8d zhso#%sxH>gF&H?rXfvFxwGz>&wy~?+Id2;n?f>2gia<#*jgE3Dx$F*+Q)V&@9FX{8 zR%T}@2pFdNG)}pK0EDU`@s=qdu$~b(@%3fAETrFkQj0`~ePu=plJ;f81;bs%b<02y z8{0%FW858shHwiL8O&UuO)uDG8le(by4hC~z<|`iWF-=;i(6F?U|_0}BP|fNDY&5> zZZWdm90&x8Au@Jc_Q<8`s!hU`*(cDljHD@JCJrp*HqJ1e+Gu z(>NYbS&S+1q{U1K6=o@r)HD+S!9K<4B?B7nOd@%hFazoW<}E7YN;uKY+PVemdEVF) zv5Y=~Iv<4KO_-%u7-*W`7|>boqw=zZ3xoa`tRlQn9s~amRNe32ff>reIUzh!AJNQ{ ztJx1BJON!T;NE26Xsj@9Z@QwqC@~%~P7~L`ws_^L*ya)KXO=%E$Z?bzPFR>O%ju|c z;$x}zEQPFzuTLrkumO!>8(uKWX;bnt-S9W(nv2jQ6@ds;O zj(pV|`B55j(mT-G7{p;TN^O!4h_<>9br2;BDeK2)(+Zh3_U#aRVB=7XS z2`r3{^vb?}oJ_B08@=wGX1iPOj7nK$7e+f)N6S;^{{OD=P8sb%F{G zOdoC}=y{W(pvxp=hW4ej^+`qYwRf26(sF990q3L*_meSVg?A=|TtC=gIqwUuKag9{ zT*rw)=8b?LOY8Q6)lI5bv>IQ<^)?Lp91FC3;k4is0WQTw>q5qX;V$M1=hXB=@!Ujq zB5*Ij%7E}7w~P#xz-*S-`UY*XR&|lPQ-s;T42P+9U==5LpzYkk-{3nHaE)VU=b_XV zzMMR{o?RQF0Nv!qF7t%9PSQ(q6t5y!UI*W7hafbdrBI~D3+@x^U!3&*m8TJ8%|^l| zH(!~YC5wk781~?8Q4|K{WA0%vy@o7<3ET3R_dWIvVGGx>xvMqN3HI0>kAw(?(oeuE zfV$SZJptLq@rM$ELc6KJfCBTd`79a0`qqPG$?e|mLU6xYZ$;r)P@iIrjfA+zbo~IR zA%NiRTk?7B5h^JEq>E)6t`BqlC}}x6yW*Igr{I;_M60DGdSasf(vox0sp40RKMy8l z2~h0N+ZVex!~yKO3Dg386tQcrv<1dcRR03=O~!<-#yxs6r)OXoUM}OpJtP{ej0^*6 z-w)JhID(klVNXh|-IaJo;@d=?3bG)@{Er~?C@hQ{aaPYDK%jR7dueX!RV}}X+wui| zSdW_t%WifdvBCszLaKq4i2_T1P7haG7{?ZJcyQcVDFSXAzkW?i3^oa}G!x!s0yC2y zKwa2z=1V-m(XGRdgiEq1?8H-p->P@$1^`8}9ml4pKs93^vD%-w126>rz6&__vObvF zn@4oU#4u1F$!W^1@>=CfH-{k-<8=%g3gMU}9HY%6;l#*j<4VN}RbzS^HACsJM)-(8 zAAJgPg%?nU0BAM zIW>7vDX(?v=ZUHM{cX>KEmbN}u4v*pkT|v8G`hLR&V3Fz?g0hp!HrV@;N55)3~&XC zxQ*U+`DbwG8n-d%qPQTq2XI&;YSQ3OjLq13@R$IX*jQYc1WxD&X=f=2LH%wrh#4RF zbJ2i&)o!6U8;?Pylcj6#0=|$BgfRNZ4Mm8dyEc-?;xi6G zr7221r?+j~e}-wjs0v^bba0Zf_~u;21ZtGdQ0L?Rnia%!*qOKEFUeT0$%zw2!6SrQ zPwg;}1L} zZr&IM<8|Y|y%%3SHT%*-fXuCa1a`K3W2X!ID$Svgv!GQqf6(X+L zUlKvcPIF?~YBHk+PI{KTprwSEguLkL{w0Z?@%N~jNGU`r z!Zgr=(n5^G0ueg2gGn5rjSwR0C^J-77-@1J;=?w0J0iwX2j@M-?_YHylS5c5N~I15 zoJC}vHn%5GaX^|0lZY5IfZHI767Ku#bI*?z6~mm~v=5<(HF_ZGh;aa0Ha z3ZZqRG&;$12Nc9sz_&~m#Ko@rI0cv<{C|$p*l%y#+_po|{k#_ny@mKO7UNOdFBQ{J zEPhbKTu3n)@Mmb9ILu*l06HeL-i7GJ(8<`IfVD>8fVQ!#G23j|6ZVI@Mwvp-?_Mt^I$>Q!wx)D0$V;@XJ(fc3_;<^mp=oAlQ zQ!Mw1h4G5b(p<2uxel7#+g%ybh4IN|d7JCmK8cNC+>0E_%$DLTw;;28GP5#s7&cZx zSsCGkjbn&&YuB#sQftptQ{WN{>L{mQ$>KKMfD*fuK5g&+m96d@*j!G?uFvfd=@jqb zq?w7B4Z9n5J5cap$Kb74N`~8U9%)3{zqlx|GYbc#25!#7TBosgfCxrLU(g04L52Sf znNR=AN+pp}|2Cn+R2w~8S_cn?N44D_34=vm-zC+y$p_HOB%p1o{- zO0*ZRT{7u5AA0sC@Ndz5MfckC3ll#ywNFmvOpIPm&D-yAP4u(#6Td3L&iND&Uh;c* z)o5NVZiPxRDVx->ZR>>}bO%k~9^^3;i({?yB1{nk0g zNHQXw89Nn3lcK+CA9{BAL(ih({Yf=@rPId!)d)>NVCC#)8)m{b!sjqntND0NPoL{H zY0c3kT5z9$IfD-X);_{60aK+;mXyxfU6_w|Aq~n>(Ug^*x(t_3^FC*W3PGDTHUtr- z(;pkaUsMNFu&&Y!N&+!nWQHXkv9_~!u+FOeqk21REM^yu$|btsZ6Ux4xl7Z8#P*9y zoyuYIv+D8D$;XSHDIpBD50rVpsZZIhaX6P#v)1B=FizAs+72809oT0W3R}5Ixr3OAy`bH)f17;SeT7ok*@V740M3Ghy$OD=XTLyKk zYGh1=y380>V_|V5B(D}A!gnIvzb9iNe6cRV`0#o%bwEcg#Gcx8{iK%-clw0xN|cdD z`_bIQv}qYgD;)#Tin?Niez_K5Bd5z(#MH+S%aSPQYTJ$dE z^!{ph0E5UHBHVGhGf03_(PUgMtp8LZ5M|8ZR~u=JrBIFxdoh}yB{jM zkS@lOmvY+F!E-^1nY6fJEPbC^K#afW zTrzdsIYUim{6I6-#xs1x-mhS`LB==CLw4GZ#@quy=ujxleH^Eff`!eD;i%0$I=^2K zJfY@=HrXTt6ek)>nkIF3?A9EIOTVV(&kgK7F2Y$#yU1^t19eEqF@iD-*{e$G*iF#t zm&MR6Sq#X9fSZLL=&eETusw*746(cw_66v7Z9bh;4E2!@uaGtA+BzqhIw~F zqyM7Has-W@`(OE2y>(#mWD%977?eU807gY)>iPC~Oi>ttMKS0Q}?N_}`S| z7-l zi`b7d*eCeJ_gXtGij7L09@kOwO@RU^s*m*{&yJo2Z)Rv+ z@Xbux=-Ua!<}#Dy`ExmK%wkTgW(e?e z)-xi-BS{~Et2|xw)C+)|JYJ-1KO>LNJnyMDt;~Ps`J0e|%<~UD%f!({`8c6U=i1$! z3`igyzJR^ULkfo^{*hQ9immw&*-Mw1ZG~uO*|KOv#g{Vy4>GQPvA8P%^b!%jv}7MqGGEE1FOXWxjZ|YfvO?y5g zCzPfy8*y0S+|b|(LeQ~l6grrNkuF3o8W>MbQIzE1mrPfSO%WZcsN_Z2VId<)FhK)= zov|-d;R@=daVRi`(G`4L4@xEv92;g1N|j!Q9_6WPIlahV$w}j~uZ8ocVqoeoa&rrVKcGvrqnfc-+41^Z`&C{ven z6b_%(Guw~DJzR|EU(g8ug}DNG^LTT!E)=fMd#aVw7pV)}o?lu@pa^=U3fEy80_2x( zpGm)p*mH}->>#HO93bW^LJq4!57X34&$~iF0&WePK)W5oAqQo@-A+R$uP)+=qfM@% zN0OMrB2T@NYL)t)dKuyZqg2}y(xW4WnI1n58YPR4&6FplW7l|0$D^M-rp^veSCA<+ z1s99vXk`IYveY*<1TJA1o;!_v#wamLPEV<`Zk}#YGVbUMhlhWUy6C#bVt)*6MiQ?XzE7tK&m{o$bq7 z9e*i(WqY<($6rCu*>SAZ@mD@hc2qFjL5y0ly(?v@E;CyP7I6@(arQ^jng_Q(k|f!$ zAcfq&{W1Tr_D64${qe*k`{T)nus_1&nAjf?I`ts-$5RTP9bqMtygQbefOM?Jj3lP1 zmX&}{t;Vb*ehFssySc*rZVwjAYIKCyfP*Qi2@k1v5xPVh+6>}_Y8$Ef3#QDy z@h!n*Z1gx3zBf<6o_>!ks;qvW|KR4$KNuS1m5Z>!S)EYT<@`K+@Dc*l*95>w7f_*}{BdIs56koal0JE2YDB+H`0eTPU3@tyN4@#dKX z6dN>z8?@o{qUYUs5v98T!LbMR-YvP{lRwftYHI%yua%V8;BE&Ma1gLTUpJZss{9-N zvg!Fn9|d5ql+Vo7DtPvceYgk{T@@*ohp2kC7t%Z`|IS+5y!UfxhQ{SMp#7AAB+>w! zMIOH#?of0-pjSWa+-~){9Tr4DQWHB3m_P+XbQ2-0CH|9|cYeMZ_YL=+-35f=qLxs4 z+Gkih_e%M^|Nr<~b8_oVMhvSnV@J5@K0 z8Y}UhF>!GKa}CiN$-5A{7Yc zf={!57bkM32Qe7kVR{W R$mAhSiY1!yDK62~Se^8u1s5ovl!(?M=g0W0g%@xAN( zHu#MUG1~8t`rz``jET5{=(E0y{K8(yhiH$}aP%#PmB{oECC{`5;vHk>Oe;xeGePbU z0B!|o^tj;wS7jI(0!6A)UVN@ylN_L~y-!R$EGjf+b%sBX3~v-z)^ zy+3x}-yOuSJuJkJhvm8)>cCGOwF+TrF$3p}TOWNMf-I$Y_@j_3I|yIIm7n_rqnu`G zLKxYvf_Vn?Cw-LQ5{tf=)4uHj-YT#EF>Y__bEyU6I;cno2pZaL-G!Ch{HW+6#z&ba zgwwM}TOa7L(i2)b!(nhU5QJr!XB@n`kH$bD;E|-+*~ba#GQ9^V*8N+*b@|Hn+R|_R z7V!^77I82Q`M8P_h%Vp?=Me!y%+f=B8T5;iwt$-alL)QQo{$Bd%sEDlSV-!9 z37unx*X??*Rc9_fNT`cQG!cyiAE*!m#z=Kch${YOvYc?w;Pd2!f0R@B4@QAXMSYPE zD(Xwaj7hZ5XdZ@;#;updGKi>xqAu?++t)CexC)M*rDc2MA?8;VUFJ`6`qSWGt2S~K z#=a=BMXw?}m%pKG{ziCro&j;28y(o)~ftS>g!VM@`aQxy=VSunU90Mv!25ccYw&UK?9VO|; zV_vF1&1m-ih+^-HUjJv~*J17#y(sJqTda$i$2SoWIt)Q0z^HFN7|crv$V(#-H%}6P z%irqHbA>W^pE)j(GZwGz7)VjnDlwkW3JH2j*qr@K{e}71ieZaZsJd>XGKFaJi~zQf z6}sRa0`IO`>1_DrD#E??;XUi@IWBBTRaW$iA4*?#BJN?Se%Ok>3sd6?{srAs`Xa2U zod^OVOnk%r+N#^O52)#k`~PmvE1vJ(AwvHpvi}wSAF2WIQ2td;;Xjln%0kSd0*XW~7ELA<{}In~Zl9 z5}Jrs!9Itv8*v#?m(g|?C9<%S5z0Ii7>5ceGBcoEH)A&ua)ukqQ2GiK3X~cGjeH2Y z^Kb{Kyr5W*Tr1o>nxGh1i9~0d#SRGscG5mw`H^*T9-UWJycO4r(kd z?RK>WR%60)8r#<(>u8 zFBqM~(AwBIH=F>#+%|MLK*VA*m_Lt28-F8gkSDw!a6@rin8OZb18ztO7pCyYd2=0v zGDDT)9FxFn3x39l5nH0#*}Rg!dAP`uzs-3PTJm>@mZ7M^*pk1MbMoWdtom`T$Q!I0 zOqoL6ny88x;jnn`(!zho6Nhg8K37=Jd@qGCO3VSoQAGTrlmb)#Fm9L`BZo)0I`}Sj zn^}PLEcZ~boEOJsu}hRS!(FU+ABzdZ?(#T@-pPS3BHqalfhG_?Ms zEN^yJ{Zmfv=gO{bsCjXI6J=d+m4NwNDFm6w-z)IuA&MN_uy=%8vsxY3e|t=W>sK$B z*BJU=MmMUGivB4lAy16_@yz!8TWWj$jle~OVTP~$`>4a>-_Z`s{rBU5JGl@i;Npl2`#jmh_Sljb@JN+bS3J-|RkHIvD0;6+* zII(OwPXEZ&XxO%1ofX51`bXmCf-5NkzWsep8@$uy+r8Z?R36m1LU078c135wQsNj`Wbvz;3Y;$ zs(doDXdgHKO4G-+D|<$+VtF>C1e8sXP$S#~w2S+Ys4;@qNuL#AK+>RL3=;F;LI#Rp zWQSwmSw$6Ksj&Jh2dwu0e+$^hA#gz0;2w!?iHlTOxG0us9E@f6O)hYpk|c_L7oo6E zYNIw9tfSTsIYNX9d{K5dBc(`c*Ud!)A?kw4=GEqqIf?Wl+~4}7Zkd{E>E0*`*$@I| zCrj6qPfS#StNxKjR|JbmLFrzwjufd5#RxEQn5mdL0yW2k7e;WWE_LDN{S9T z;&G}+Q@C3Joe+pukS*F3trXY_=JH|dWE5c)?&=&Aa%9@6XqUaVoVD|;vVH)f(M}) zZYTpGdTLCUM4t|+-jHmMB(EtZ)(o4R$$nBI zI*_3T(?&p@*V9&lQXj|{Q}dpNhJ=zZHNoeN(^Z)TvYFLF{o8_-jtvGpS+G&GLS~e;6%2 z^Gvhbhys{e8DECm6I$J8nEZR1kDrm+oX?0R8l4WAQx-il$6lS6!dN!~K02K*o;80~ znO_S0k1HW3ZZoe4dpL|;o$j4eNGySTa|u!%ox*1!d|DONmU&_?aTRwVrb%Wi_PUGv zpg`w9G{v$E!Uvn8sp|&e!?KaXI`RozjZk~rw%8qiC>KeiRE2f7NdL%>YLaQi95TzUa%mil7O-h%n>9G$IQLfjMz7{Ks{%8bZ0{~@_g zyj@O}XZ|iJzD(3{aG*ME37tMZD<@7r>pg?l&o3hZkalpaATC@k0FXQ2YF9ejZm$6T znh_1`o%GgmI9C_%nRO-rH*I2V;_x~^d2HIzQa&#XJs0@69@*3;0lL&yTlRLCr6rc@ zKv~JoSs#`2J5{?cVQub*u~Q7(iRFWO(jbO6-FD!em#hF|JewKc)i0BGoq_h~71i_` z>bZPz(c(5D_K{rT3LYkyJ;iXK)&YGyj6{GI3FJ=4CMdQ3CcLJQh`^!KryXSu+&u#F zX`p_xSwG^bfN#@dWu7hCSyfDs}i>F8b=Z(|;>N$9ySRG;s?$)co-Dh4l?peDv(OlZ|Y3_A} zC5~x9;hh)qded!T^J2GqYj9|4rB2>ty?Ik7yKF$|C_I^tEk=S>i<(3**8x#lFRhKw zNT06sm|3+PuW#Y>hu|}m66xjwy_W13mS+%o5WX{*MO`-!hYTO2<9!k~;Hn*ms!q4G zCvnWuf}89LGqPyKxe41P7mxAG_LKH@(<*uzwV;7QRvSTmO)**;m^})dYZ}yYAbt>} z16C$T_fy|T1P1XgTJmv3NIHCndKKT=S?vSA9Ou)_y)7RKu0G(GX?(_&<015+i6kkv z{<#ZifjS+eNd{ZTZKZ;~7z#JrQS1NHcEh)l4=6$Xw$VfE=Wbt9<$k0ou3*c;qx<1iXE>>iT9 zO%r&Z2;b199bCt%^4r(+1$LcakkGW44ndg)BG8LP*~+H6`KUH#P)7_!8Jw}?@*S2o z6`K(m=8w}R?BO8M^$hOb@`~hj^(C2BwX_5RG(&??QXc7cmeif5WvlU{_skiUckD#?8 z_DS}T7(PV$%)x;E77d^-$&R`+fvdgW4ON6>N868;1dLEG6>92jIvn9d`~*D6?zLt2 zf%@7pN5FmqA}*vJ6|u6Ekv;xWrM|;7UdhGt55j~WSn~U2>J2Ysj5YHTW?yeMaX6xR zvE!lAC;uqSTE-89R~(NFAtAi0-_cXBc+uC9v_ZUqORxG!9?r*9+%vp!#jEu@UiuDF zB-h(Z>=i=2{7c?FF45okRp-%%@*RGySkX9{UDyi-av3Fu0%6KRbU!Ll~g z6rAiEQ%@hCT%kh)j@BTsG z8qq~a@j>q1B42C>_)swudm7gVca0HY2$MksDt9TbXCMjlC-Txp$BJL-Vi6a80o_ZN z5QIzIhNB%hZR#bUW^N;|u8pqw7)ST3cBwa0OMP{8cw~%AsMjjuHVUiuk7Q~3g`8Uw z)-Sm`SpR6daUf7PIq_HXsy;d_lmjWsj!dn>^z}2<`RY7YK7IXJ`2{a~m@f|RO4=C- zSHAK#Flww1B2bH%evI-8t(S!Gq7 z&=60IB8uo}2}8AZyc*e~?H@rnM(J}rP%5)Y+1p<5gx*NML>ReaO*nQtA;OP@If_q% z2New8=^YZr3|flNo3Gmu+-!`y(e>nfEWkw+eGAl+55Qn+##B;sSDDEOusg!Tnzp*p zPPUqk)J91$aCX#y5<4%c1!7Eu&Qz>%7-nZ0Er%_0RHIrQxE8E+_jA7AKROI?8SoZ% zgZGuw^6PYF3!{KmKiEAXP?!)@)Dh&hL1>wT)MEL#$${#VgzwR44*yaIo~k2xih8*= zkbcpZU)HpTLb<@x?66DflBRYG|CZcr&aisM2seB6J7TZ0G7v46m_%wMMy<0JT^$}` zsZ`?zMf83!C!IQEWZa*_KXH564W+yUn=9s*&Nu?ogO5jLMF3y&N0$UAQAOH;VK0jeK0(S|K>0f>lpqmC&=28?d*}YaIgP)0w{IG#FDuslTllb z1%yGCGVkG1K}fzp9oRi$>l}b(i~$E3`>e#nTbP(S>XYdd%~@3vpe(7<^L&m&FM+v3 z@BBBM8^I1DcJ8`8Z*Jx4X&is7#ER++lFL#no{*a50nM0(VZkn@-Z`qzXt9>pCD2a5 z-|b^xP%nQ#RvD8$6Hr&KZS(bpeOp5X&O_1w#ECO z$}gZ0%F_5L)DK{V+djzum{qx!PT`8})Cg8=zzUCwt(2UO*Pk(o8l9K5V8pU8ZDg*= z8~YIaDK~vduUTi$(8=JHz=vj)7Qvl+g!2=!MhG}T&<1Xr8t8DUN~|qdLdt(~3248? zU|fvkj*~fosiQ|ynov^gh;em{8494nG5n-Y2^S1Oj256b)*!}2{&xK+b4pBfGVIVP zBJogcJrA(OD*+H*OGwI1aV42$+0+i9Cig>0mWL>b`qm3Ce9+-Ky*2((cYqqs(0wx_ zGCvw%3)FYgkC$+&SlKGrB&X;H`~8FVZ{Q$*m-xCz>j_6mA86AZ(Bh{ zP6Q zAwLHp1Sf%aUDL$H9FEWwqZiu7Qn4nKBP;eqepv#MYsoT&;bL=PC_=Q29Dbvo+|+I| z^b?3568Pw*j)@@RTKKoy_taaG9!OU9>JqXMbYUq;WleLBO?_5(`v_yWs8A7fj^bd* zI8TR*9Ex;CiU8P$xhdzY73=UM>^Fh{c(RS2h6#}fdYER$6>4O(K>aZU5{kdDpz+(- zq8?+X#R0_Bpk)exOa2PPkuHRB*pV6Q4z6&3vL-adaXsk!Q1@pfD8*ygpGK2n zyOTLdtZt@tnG?mY$2W-?WhQg3>oO@G1sM$GfzM;hu`k({F+2n!93CFwl9j-1~D443*=HsZ-Hj%eQ(7g zm{IGPC5y8iw-AR8XbcF4k>Bg>iyNQ{?mAgFk(}4JKV<@vAy!a@mGOs8VSM@9Eq@1`^O;e?8kteE0mF8k9mFDp512-jWY^j^8nYMiF|AOx*;PPlzEXvBzPGkjXvAtDRfyx$iNVELRIaC4!^s$Vq$uEJ-0sbW6k$TMgnh7T(QDBA-OOl&+a(eOirTSgh{83wv4)VnX#KR#` zvA++F+iFyhBZ@1-#cn-&fxoY@h^q3`w5_MAKAtb^yFedq2*VbkQMo-y*)wLZpIPb1 zeZ(+n6s|w3V9ppkknQHk+HQU~WxF{-?6)1?Mx!FYYw;RxWkzX4Tobi{9NVHvugeDV zm8VxEW?IA4f+Q^5;>201w5Tse{jqKQbymIbsLl>;BhrQyw7Zt+H+kD)gvvT{9$EWDNhibl`o1#&--MiRo{_QjBO^nDGlvh1z8Y?2r6)b+|4O@QhB)bcBi+r zDIr5~rzB_g9I2OSEx8052g{dSUww2bmucz%9=(7Cmo?&`$~ z$)b6SucSf5SX3L41pWU`z1NJsm^ZofYrVR7PH<{I`#==t^OnN=QHsKRegrg?)I8I6 za8!Y^##CnWno3aqGVW87{&#?q^3e#?)CpHuPZhS9Ep8Em_N&;9L@wRE@meGFZY*uh5s;3RZ@R7GR>I_5Uz2UkhjLL zQ1_QYHEc@Vm%}?it6B3K-h5zHMgCY3UpA`={x$wD_cwXjZxNPx*BczZT`!WBB!g{3@Vs<{5aq;O|1os(%_5M9a{xtD@vatg1xD ziCNR}#%%n5$(M$hmzA{aKS^BfKEjkbxQrI{cpSN$tWG*&KE%CH9_c6yzm(UX9HGy#{WZE(Q0^CZSOxQTqJ_YTR z-PWD0fDRDb2qqfbkaAkP&YT4l)_HzJlLo%5f!s6H9Ld(=$i)@K&iqM*mkx~c5`5n{ zLO}TkH076J^IpeR6lc5D{!F+>q)Ra>VZjzA(c5dsb5m}^dc&RR1*XAh>W#d*9hw{1`h)d zn{FVkArt?Da?e1_rdJxZyr7YX~*%~ z_c{VF#W19cSC>M4NGhEL?i{3XX4)(Op=(w$%@VWR#h0`*ihv&3TP>0!Lh@uZ6QH`- zqk**XrK{Ee!<%<~NllcKK=}Ov^cwjqIDk!I9zybge_y7PBf^VC-(@IQ;f=)rPMM*wJ z4U7SMpw8&aP<~^gKF-B&<_7TviY&A$_zEHr$l9njRhxT3%WS*GbcJm)24rzT(~EdOSo184Q6tw7?g4=~@=H?YNS;@F$C)@nI3iap#3RnGtURRw-y@f#Wp<^3JAGij zHzdoiKz)v(7~=aq1}eeo2&g>0j&KYk z6Jp9$ik7bUT7G#>OY{JG7W;-hfEHR3eL#!>$>JM)UgK2-u&v4~(!>ORh%DPArc}Ko zY6eKhMItJntHiE40&mYdJJVCK~$u#m7w>w4ug#bta85n^b28*O0&Ev1Bt+QpX5<6J(V|=w zJ=wiZ2BoKjY^sx$@41~rxKDz$AOU5`ruXUL1+UH1ys#rF^+>1& z-#hJ_ge?(UUGSaV+nUoBoV<#nDj5*Z(lH>q2g%0$Dx8m<{YLpJvQua`Cko+15gyC| zlH*g)NZGh0c^vg%WCmzDSVS4RNz{cvi+_cm#xgLl*5EK>3X|;4_7Pb3yU_`fLp>sM zrGj!EezQK8+N^OBlN^bM*uB|Ud0WKJ+bmPM#^=*1fa{FTGdX}uzA>%aM}jO%{wDW2 zEx}89<^(8k6D5C4v|SX=zMesZ^Z)U-zZ`G>>G|~T zmy`6*e|GzRPQ=NI-G3jZR-qrpeE7CMlD4s;*`9q2#? zGLT)m(1k8^pbN8*g$#6|16{~K7dnu_@AG}0_kGVfS68y+Se@x^hD@z{?>X5hiZHZq#;#=dS=^)xOk_jAk_xdY zy@DcpeIXcydO#3Cw7mGa4hjZUC*$@am@t;TYl;;WsO*^9YQcY+&;BXY_?QizHPGlZNqwIA~w+2Rs-m z(nOFs!K=GLj^R(%Ki^uHcuL-pa0zp>$w!ih$c={e$e2!HBjV1MRPV^Vz_PBfG#8c? zXBA{yiM3BMPADC{WiSu&B(}$U&xh)r@G-G6bVe*QuHxEfvp%)oPNP_~l*rN23Mv-3 zy5wQhQKB{t!-KzN;wkZ}$#K%0kOD&df1`^(OsAV|;61(GDkD-n1VRJYUv~eAnSARh zkRK#wpEbZB*9@9 zIJXanI^%)R<&&bP2*%5-_EB)xJS~Gp3Xip@Pms%kV|v~HSaoE z8K#x8{d(~B)JL$LU?o8Iy8RIal%mp`+{id2jpuH0>ThqE_>F=40umxkhWb1}5)Kg}!%UpW(hWkMLU{WwRyxZy7X@jq5WQv6elW|Z- zM)C!COt|SZH5!rRPr4}Irw?-85=pS^S$O%B<+soF(eO_SI==> zWs7Ox`;%u^p4XQcgnFpyy6C$e@haWh{^;EOtb($ZGGvGJm@t~CAB&O944;AprLluL z6~1Ocjci6y#OVQ_++wjsiEJs!K@oUXdRRf=GBpOP2eq}RG>m0#^^x3@mI^g~b=@70 z?L|b0=c9xXO9Ge`l0fG+VUnVNjJfX7F_sh7WT8}cVzZo(?23Y9tl-`JLb%T2HK-{V z3=vjtfi`O!*FO{Qx_*3-le+}3O|}@@Phg}T1AP!`89e)rh07@}Q_4jf1 zmnBA%HYzu_Kc5y0t$bIBNy7rW-kP^wGu(BuZrY1cuR45rJeL&GJma*0K<h2m0fWebC5jGpyScw~0)t)UY54jwYc;f^{ zpFGRRV!%BOL9yr?p4qWwdvTD|UdA@h)#OIlHlSVs5{?h7F>1QLZ6$DqWRFnDvEUqb zOvu-?_t|zx1rb<2>neJ_$ZgmR3$9}lXv-hURg7a>nyK})iM^qKsgaV>vgr3JYn)I_ z>bJQV(*W3+puj$a>+_9l*4c0(*vgTsVEfzS_0tpeg1}WEo+20!>>6Oh(IfTor|bW^ z{#5<(ijH%-Y6JULVFMG62LbXldQbg~BbhR%H?A-Hb8bbQsQxw%kYq?ZB`DVAMp=5L ze!8`AeL@j_+$0kP)R(sv{yw$5Bzw7i)k$2_957%N;fwW+i)gVJ{AW;&UnyTwiU`qgtt08=4g~NAkGz;i*C67GnoB?&lr^`B z>LOCSfX!YM+Dv^&MW3>%AROrL&R;=v`;k~0CtQ3DXYR`-xlxq?!;fM^nu<(8hVhso zM)Bx4%V0H`1q`J&Ks3C-MJA{MaI|5l*|fOeBABG$<-`?mqdZEcN01Cv03K`LIB*@V ziE2&^a>GU|iU(Nm)Q+PB5k=GoUL?8Wx|}8|ZpJ4x15a224iZd1V{2YEhlgOom9tO+ zDKNnU97LG=a;Akz1ACXW1nLNzb(gj@N7Heo#L$s}jiVzz9nEJ9;u6FPQ5n8y(F531 zma$aG?%uSsLuhL&Xh@=D>k8ol6~I#_@`8CG{ehCN34fqDd9mRaR(e9DLPzVyp_gdT zj{Oh4a%WH7Z;rhaa866`dbUAY1;defln$?-v>5>;$vFS)ygo|lZL(pwz!WHexCmw@ z$y+B`gc7;NE$Jw3maUm9B<&Amp%36&i63AT)7gP?9PPGmNq{Eswmm^?yIm?9RKRoo zeghH)mXVKXS%(T{@;C*!n!&l4Uw&n7E0>$&Uwy^Q8ODLHZmx48NvqH~HO&4SkTujw z?+oT=kO(pLne&#?&cgS9Jdj^LWu?&1uT7rxfG~^fCz{qCPYby1<7aOWtyYX~!5qIN zSU(N#jXCYHV$|&(n>7C;@t_d9>#)zfmQSaVf?1U{9@m!84QPTO8r;|Qlv;B>o&DDI z>zCFbpl(f6leSgthXFhygp`sG^^`mHM)pYls0B`zHX~34ae@gIQK_&V3DEk*LSdYh zgAA%>89>}fLlqm|+feB}L~^ZaD~RK=TCbEW_)5Y|W+Y)~X;D(2#3Sfz2>p8%j25m@ zbdF*GDmo|3R-BuNcUw}OWVSSWT98}2cRPD}?T%)DL3{5)h%mFIQ%oU3QTPfoTj&SkR*?ZGsHu)eqZKixz{Cich%H_RnspYBaKSl> zHUdpSMr8+wRH-1he9fkanE=jVSh79~G|gah4PqauaI{CXDV2OWHUdan#`$lQ4Pn3> z!4kp0(NG#QbK5`QCmB1FrikrRGK6S? z5aP)3Bn-mDDbU`d8(PJ=lmZ-71n5p99H#O)m}ivc8PvOWnUt>+7GsE-YBa2PinRnA zfa<#!NL`3%P@upEFco#BvUJ_c+hL#bSSH5?f|vG zn4q4ENJ83JC}b{SQ>+`?LqUbP>!eNti(s{u-vh;Zbx~&ev25V{`b72=!%fwjrxYuw z0CVFxX;34PfO6Y-1G|j>?LBg6?(h+1@7L?LG;e^ZAgh#X)6a+4R@NhWI z);YtOXnkM^E)YPL$3w|z6h~Vp#)W4ilQE}i(+){Shv%cQFnk}OCMuueikQnZ&s6i^ z8}-sILbKJdnZ$yrXXKMFt)$6MiZO<{msZl;{&jik67*tif;4&OIe>+8!*C4z#o`0| zvIuJkWa;YbbXa()^2#j^7A`E316mmH(|S+TojV8R1*&%`H(DC*66I#s2qV_a#>Kmp z9Bi`^8c-)Kc#2uj*mcAxLXyp;)Z3xGDg?vcrB!qwBAu(269GPyhiqIoUOKO=sax|^ zxutend-g*Er;|S>VUiYO`v*)Y$zYUWU*I_frwK-!M4g>9*ITW;7)pDH$*sC93L=VP zRlU;d09-~LEP~jAL{u>V9~X=hGr6nT%m3#9Nli~4$o}y*89@rHK(IT%R;$hBgZTh? zk*jOu%?(_nAqC0pWV`0spsPEoHTcWDj7Oi*>UiE_wLD&4+c{&YvOas1z$=MdxhecAoOKT`UPVyVSH;$aXv9@t*&O=G5AJGhd zf{6<8g>cl{dZKYW^>Hz)4O!kN(l-cz7;+lZA?e-xS^m_)+E?up*IF&1{J|K)E{nFXX3ue!r^)C#Xmjbz+0C`zn51PY{5RQY9+a+PRJ_fKTPuR7+|_9=jOy zzl8{kcB$h?m1I-Q1+l?x9^x@BHPEb!0DQt8R#&0`76a!?WN)QSY^xS4!$l$>pGw@W zapc+6bHr|V3U}6D`v%*0;ux!NSVDsv(BX4RT4;b^mx|Qz?7rG-@hx{07+~bQ>$bZ$ zjyz*lcR%awL$x1Fd0{5)OFYcb0aSG)G@;w=IwEt3xumbe3J2Kq=4jn27ftrz+U0-( z(G%%bpT_m@wOdTjUk#82XavyIUWqTc{k3Fu&;yM#x*UW7w-={Ys2f1_mUec3?JF~8 zM~-rO#~v`YvuTesj)yCAYMGWJ+=P$R)V)sYjE*Qyuq_L-*kQUw>Kh#L=gpOA95x+U zV~%jDReZ$@8^I`RHrebqYHy_NB zq@S8CPUqu+`DvK?$<`vYV56NqP$M@al&aLpp}dzwI5oaEsf*QbuE`!QG#B$Lsx*Z= zFBwtcJdNX_u0$jcAFX{e6_yCN%LxT^!8||`yRw@b!>lRb*2ij3ayvZO<`)1(F!HiS;7vPAWl= zicGptS`=SV+d}lNhh@q$y4T_Q=CX!n`+x(Rm`?$Ym{o}%c4233~ z$3xq)={DAd2%dBD^xmR}XZ>4^#dgZc()*-F>I?_A{VzXuECE9r&tN#Htd_l*Pj=QHd*X?sPp0F`9@%!7;{_OYG0tdh;IgV)S=Fb? zZg{rGZop{IcAzdi?FJ{^CpP-@yGma0b z7KS4o(w9B2J=*3>!;#fFZHXhEWWH~{QHfOTpt@SJO;}Vpo!B(i@;N4WD(2r&o z5i>6HrMFB|j!j(T&kC)mkiRNnv0=zYW_{?pOzA-itV@rjMwcK90hTyz<6@#|I&;;9w3HCX^wAdM;FJ7{J zSiQ5x&&b%gazUw4Ocd;J!SkoW0F02>(ahrctNQL)7^;SCzNUAh%eu$Q>A|n0NyLK< z>PrOrG#SM@A~XpIiX(0FM8%=Jwn?oXL!>M}@dH;~b<||M76-dMo>+BF4r4E;>axhL zkJXEEvLu(rVmMel^SaB{Dl)Sa3h7BqX zfO*b-cA5r9SXMe8G3UWU~{C|v?Agu1Kv{k|DS z$H=5a(l|KWwNPt`%Pj?jt2X>rWR^Hp;4Ih~%e?Oa$G>gWM)!>4J7P50-E{}5IG!+i z>w|kk;k(*(Wjo4SvRfQJ64bF-5m{(=Gpc9SL8xeN-uU9Pt0Y&hQtA+=V0q(MvLP&T!?9~DULZG@a2*9%EXK~UA_I-VXIFWogGJ-p49+)PV@ct~ zEM^BV%#OnkvqCR@fobERocBf7-DeR>VsH;+=TK;$m1tku;O5Y5AY|OzMT=9|tc}{n zX?1x`LU|-4Uf8hl6rx+GaTr9jF-5RZ^hqjNt@dnwh_1p7t;iX=KvQs#6mUk(v{NlZG+aZcKZDmP^+*Wyeq9}eZko`rj#uH8|8 zmhaT8=GnK4nrAb$+Mtgs3+ACMRmu)om9lfiEeI#HMgAB#>j7f(<<`Xg`I+)Zgf}k8 zbG7`xo+jMq<<$iX=d+t*RVpENRtxT=Y7w~g!@5O-zVmjSe2_;DUI@vNRLgj2Nv3tcECM{cHz*( zG(b#-c2t2Y#Xmg8^j8eveE9!D0Mpdb!^$0<>5X))old`%Bw|)U?7gixjdySmgUP=h!#&xlNaZ0{&|-pXwBaB_&rCS z$}uwPE?|u?5h{dVCbh`)fuKu#_QKS&)3Z}VKhR!8WumXO2*mStEn#Vmtd-j_*zQ8| z721N6zj}384{TPLhoEXIk%`Z1 z1;uUYwhEH$@G2bL#aR!PY$7G)vw;L|C(qy_UUD%b(1{!X_rl zj~C{h8NAr(k?^KZJocffXf2WQ)>3#etI39WYC#52oTW55pW5pT{_ak5P)hrSTK|b| z*j6??KvTs0b;_Q;SgT#kTPoUpWL?(4K>mjF0ZO`NdFbf3aqIP*HxM)}jB=@#4~{%f z*X2=!wGP`sJT1P;UhFY@1Z#pc=Q-vsq@fmi71L!T%?1$7y~&(#bVF zi75y&E0R>9Cvb9Q;z)P;_DC>P*z@MWi7qvnpF}+Em(j)%S6Ra3dH(}DOpe(CwzTu? zKH+O|e#_C4h8Y@g#o2wR(Z(@Xbb{7{#`)9j_nsJ?%w#0pb&c5=!pU)wGBD_*Z@N<| zLa)jx>HOu@VvIYh?rkF~)u^N<@=N4)vAe_Mhv zv|2&Ou&bmz{Hi-Y{JT5TOW$z4^!K3wmc9;e_^ueES-OM(3ZYWZfrgy?5~P*W%h|MZ^-wuR(@SquudzxHfEtQ+WR{pHkNxBC6@DK`_gJ3;;L&xh7{AGZ95wftM3Z?|3wGr84$ ztlndE#lWL^m=jfXSh;mbu76a^$MdcMx;}0mr@Fn&H6s+$e$I z7Hnz~_tiL9^K;GD!y_!Rc;r(gco=$%9ESBaPdC+qDY+}cor#vZsInp}y!wGe2&uaOs=#)QbltttS(($LY{^+T!4#RaWyA=OS z*ZI$D*`N1VVfU~i1?6}DqIPHt2^b14+%bs!eE7R6tp0JHpGkh+(zX4|?FmXrsOujI zc6#uY^(Z%mcox~xacHiY_g_*)<80#61s-FupDVtu7kBno&2j%qK>zDn#(!CD;Lr#? z8}wKI+k@GEDG+{(S3>cq%qZKTL;c5=cv@nn$r{J*#wO7{bGSer0Wm=1aBw zY_NV;$$OUl*W#$tCJ$R*%?lu>|hWk0<2HlE=9D{BIsNNq{ZhYC>@clh`NvG1MyT3$4>Hi@L z^FM1D|Ea;p&$7SkaU6c-=xmn&R6cm@&Ia=G&W5NvOQHMa(s$Ya>M^n%G^&08Wq_*9jX+n&Vvj?@GES9vYNlJ6s?B7)U0H5KNOK^Y zt0Ov2Vp^F=xH5t;vhwfr`&TS_*g~RZJh3=2Gn53TKy4MI+6v(cd5$uTMN4h`9S-CC zj4sz+Gl^cc-lR3OO<;jxd zOAdrvb*dSbk7CmH^@WU5yjmH2iHV3%BM8Yo~@C0TpT8 zhqfNqfjT|TV=9SkhVgYp#q&C@B~eJIOZB9JRWsBF*|uBio%uU*1}7%&_}#yjW_BP8VV+rm)i?MieOfGzEQ_|Zhl^DO{DCK*+30~y)OWK z{zmKfptl>hc7S}}*rhV$;~V1(t#!4g+V&_EaJ1uje6e&MrBNE$&#LFqi_bUXL7WR4 zc)tfSlarsN#!Ag@7(wt7@MY%WKnX*O@xqc-MCfHv1MlP?#|F+}A=bPK5{yUe!~ef! zWb^J;xjgq`%X2Dv*}~wu!yz4{e|_nCteCR!a(g`JG~-v6Sam@Gs+VTFlln?rd->*+ zMrmYSH)qF_n)XfnUvyA3v?p>!fmSaKY)`rJW3U1cXC8@+_DWkrTDA?1x%Whg6Nrun zAq786M5X0hpxr8`r3DoeT9D{`n`MW4C zKfOZMoKs%~o3wUbQs{qmRk=D_0_=OD(lfw&PT)>RL{a%>q=sf{2wc5Pb)#q(ca}t{ z#;8f|<eVn}UwfoM63JzZ6x0+S1#+i*L^821) zgl6|PQrMTv!8}sQNH3m$-sYY)JZd(c)9Nt{v~Gn}7hJbRi1_b32jZ z%^_Pxzfv>{EF}aiDF}zn$9VdX-W&4aLj$x%b|UmT!d9ic&n~3}a&}CdOillcaqd9F z`iI|mo=0s>iQ=045?@XyXIzy+B~?b@^+tN?g-wl|utp7SdZNvaTw6p_d#Nt4=!K^# z3;^$yc{`4p<<)B=&yOZC@_D;yscs|W9?P$AN`u4lo6{~Z(Gxn#;boDrmxzDg&}^t| zR*?xa@?3VS!CfG@Xx>#|=5=i%;8jK0Di$SpMA2@VLinL$NJwls7B*oGy;KO*QVn{@ zyB`|*)+v)Qp%?Ksc^fdlaE6Ua6te`6J#Odw9JEcm`>o8BMYHL2YR&0X2iePF2Ei=p z$F@<*P8k@uX56>tG?gTe9wEzzYbuu99?1JQR<~_>Vx3X8v9A?2x@rT&56?cC2jds= zKF1yI`x+E$odgfbl}acMPW{4wm-4M-A`$vYdnNrTmPNke_0rPPAsnjcC$p3hLiQKy z`&l4<++wIG!cr&zV^I_$n|%C%@?qUXjiC}?q_F&AQ3UjB3Z)CdD^+i2_ZTee28F?i zXf>jS?T0T)8*?)2u`d%F@=u~SgjL&P9)|K67@I1XQasv=uSAH-H1)D#NL8xdwg*H^ z8z!tV1joiADva!PR2DzQ<~pm^Bf7o#%{u>w4SaR;+I4BKRM6zO+Ej#uEo`pX-&lf9 zi#~0rxSw_(&V|q#72yfT%lxsg78PpzM0#I>%pa%w(faqX>UUX|)9JYi#`q6UgJCyqJm?J-O zr4}^QRW`jZ=`5qN>C{5&jH_$z*gGDJJ3|cht zn~pnzwLMj)4R>G|DAp#(!jgYbH;9^{Adn7SGkmL2bpzLe_7-42QUEy{IP$|w3FNk+ z3IZ4_bQ@^t9@wo~B^XzFp@s8$(FwcPu`-n@(g3NjW!mi(G6z4UQ5M0lbYb5PGnVkm z9`p1>Jscr`{G=)Zc=t9*H2~x1^y3m06E-LswYmc<-)iFRbpuUU~`lS89>IQ@JlT(R2_bBx>3W=1z?*YXn< zrvWXEvy@z`9~n{QUQhfojEk>1Q;>0$;8>wse7Q@G3{|7TyS_=?hAZul=RJ9<#hOV9 zg``@cUIc|;8y@2;skor7A#9Xf@5rDiS)qI5fdZvHg5QOdLX37mrr7|xXJzz*PMj;# z2|{4pymK0k^lz;nz}9){BY-^ zsaY9=Tm%%haM>U`&GYbGGcUH5gp+ooYD6T* zlBarOi3&4CSc!y+6GK@drxH~Jj0#5kx zOOMt$_%{e|f}WDnta5BHKs28(n=NAcQ!2J?az#pJN;^h6cp(>JAW0$vnTnx%>ZLzj z;Keo}Mnn4uS+VR9SVOAE86?FdqW>%IzZUnp!fpi|!p!hGuaY-aZ=Q}Vfr5FZj-Q2! z?b#Q*NIX>)D8dho|6WPqE!4zm-TMN{3Bt6omz+cls>fAqs{?*)=rt_S(t$QyMl;a3 z@uk2=J#c4~6LC(#bPd$5bp;ozY!|E5W`195YT( zWQ0|c;AGKfUD{mOa}RKuevb22(C*$hJ$o0`K*(M*&7lCJz4xZSXV~dBf_PZPgU0y;rhl4j7Ll5n;HDW&nI^K`#}%l!4+?J4Pr()y z;oU{Y8qHR-YijJ%#X|gE8TJ2K>CV+ogn(){dJQWr{;;iT5aiUMej-kx%`w) zV;w9FMF~nTt**aXez0_T!>wj(x?p$4ZK}&q?dwNsQi>vHxc6TyC;dNm2IBDizm++KhseHxV_x&bpd~*hRQ$vZrW- z5G~Nf#&Gfo?X;>%3|d#(a-6sWgYfMyhVbnRNwSzx>==DBCQs+cdX$9Z(r|-<)4mY( z6IV!62TKMrDvkaqI41U|o-tyUEEv*0=>ng~wW@KbK{h zKZDEm?!AN&uxVd#Bl}5-Lkm7G>onJJZh7HD%|ku9$vJ9PS~^%;$~CcqcQcFXm)i#< zmr`ps>d!0W@+|dCv)VEH8^p3YrjrpIA*X~`=P$;u%**XOQr_6fDfb1PGRGu|*Z0)a zI#xW?fnv1DRM7%pinyCjsX1|E5{~EM-zj!ea{k%)ZMrYaQ>$H%UsG<@D)PiD(O#T> zmg>sXyIO9?s3%H$f9F{U$W|g^d+FH2&`=Ky84FoQXSi)-`o`j6cKJu1U5yiG_o}e@ z)*)lLa`$sn`TEtFMaZB$V)7#hIPM-VNT@|2mSN&XL+W2&o{jdlm_^L{D0FObCfFD$ zc1^8q0x-hoIw{icVF;#aoNtBU=dzEI)` z+|nggp8o3dtRpx}B`k*A>=N%!g9mU@QTjyqlb!p#>|>?p|X$G%*qILXrLmY z{KZ*Uz;G-TVJpKijsSXCUHo232Gu7RLwRY>0`E#$%4Yh4S`vpropBLZT_ z`u9*;`BGkfuXsT~5h&SF$+-!|j(SzHr|XJqGP4uIwyh-SPgyzMSE+P?^%Ay@DFnu! z{JFt_i(+5)?W)#3d9*7VY;ej0i72&fp(`ra#SbOi;$3T+1K^9LqthWkC;~}FZn~-Q zUp83#W(1cTBtW=Mds*H>bzS59?YYL}=hxwtmy{1+IpI_;o{iXAG7dH-&(U2)@WH?( z*KhXQwXg7%bptB`iyVS5H$Bog!I__Jo$PmN-`M(6f%);pNoE4wKw!N5qSZsS zIsYnn{v*x)RVOv(eOt=gjai-m&M*ROM+}5iP;4*slk`7Qv&{0}zo$r2@9W!A4LT%x zpW+O&{H?)@R&2bM{n0)7pXWNUi3t_;U0b5P?IHvf-mE^$?G6=s!4LJsjD@8qCaRCX zZG*@3LlYAkU-E|jMYx{ydB$R%xl#oWseE)Td+k!cGzrf(m}|8hU4k296#uVo`% z1WAQUYT54}$p3mjS5VAVT0>tpM1Y`dWwq=d_W1fYSjyd2pvI_V2_NS>zE4V)! zQn#>$rR7?7U*GM?0)RS#tK zIjQlR>$hsz$NTp8=ERvw^M5oOw1zlvjYs>n8>tFo0Wt6%P-PYr4>A!9Y&#fg|mO;VE$+ONq=mb(#-!N z^TJxVd=QlP`Q)w84l|YJ|Cd=kT1jfzllwe>oT)T_9Z6Xs=%f|avZwZW{y0-<{!eC< z99&wqGHt$yse}1{-A@dtOjlaTNOov$X$>fxKDfWSnr11@JDgpQ5&_65!_B(k$t>m* zq?Vb3`7ieq9NW)bTH_i0q z@UODTq8;Dt>gqBG@+bG^f3u%Q(ludeHKW{?a^8F``^$Ux`L>j&E3M@5j4G+?tcLq%>J+702lGz$)knxJ-i=iPV3+&+w>BS6ayvG^?I9+KaPiMoP7_r_$ay{L@zx*LhRa(I_+2M=Js~f;zW}okyO;eirvsr!0 z+XXQBx&HmtS>jBk`Ojp-r`A_*E)uN|-MzHWuuoH!RxpzdUtYK|i}76zxHtEC1!=0% z3KlZVTLaUfMrw#nr`~tDY8PIMtTscv_%T)Szu~r)fqabq1M$<_`Lv4DQNC-+BZFra zsX(XNayQz)pXak7o~G;fVfw}6ut0dH7Mj~|WDBD;(O14gp8#_^7E2;iG0csfC*(Vp zmJq^2)D*!Vqi#8cxa%h=jZ`XYV--lve}{)jvZ?}dyt{DY9gB2e$tl!CVUuCBlu4=F zxX_C*)Yxdeps@P1necYWQI;YIarIi&>rx0|*b_cuocF63*xHTyC8Z_e;Up#`L^esc zCWk~G*lg~Cr!*1MOa686{wZqc+4FO!C+gG6dXg`)O&!T~74*S>N?@Mn2{m!Go%17X z0wJ0iL_cwnwHKB|4U^4{kCUwF38+LVdz4kN7DW}~O~;qWF!+nZFGZk>#j^No)(h_Q zQx~3{dG@mt*$d`zZ93kK7l+)GBNhKKCDn-~BAN$ysIfNzf!FADL#f4%&Mf94*v(1K zR5tpCMK37xJ5@Z=_N@$e{R>UX+pJt`$Nj%ZhU*lq0XZgtY>yjO7yZx2gAa1lUwpPndehB4z4)rl zINL135GwP<@efmp`Q*fNmi~M+A~E0#M_`Lvunn;njH5*0vpd@uWrW(JAxgOA3FSmA z-{k86t1;nT*zC`=oMHlv|(V%f4qkXQ_~ z3}{{SnQWdjI-x{!#TO9g?=fNNW>Bleo`~16xW?PywI!5;*x)tC6=T9c>2Sh5(aXT3 zlOrrXbVAjH(TmL~9ieu2JeB5;4I9CeF8r{w{ar9su_QS5G)D2(urSLaG%L{eQK`UC z6rrV*Muk`92_f$MVD4G|1vT&q^b`ifoftv&Xc0)EkWfMD3KR)AL7JzPh+V$b$iA9* zg3vw`u!N+Eh}s!7vZ=h8D(N-}&A}?6yi5|XuG3kLn3Mt<3Ig3SNqwYvzdl)itT{Tz zTGaD?bIq>HhJZiB63C? z!^cUROqA~JNFrH(FO=GjLVjQGUBTZ<=Xm9Yse;;7A_6H4XUwPActF~TpNlTBrP-sl z%5-K9HQ00+Tekp?%prgKvC7+>-uPdB{pqI!c&E7E(->X!ut~{9-#HzhU4H{xmPe>DZBN}y23 z;;t=jJdBRGg!w~(fYswWszBqm9!1ql&B@hHQH<&Es?+moBf z6fC)jE7kG9L8Ui`Tu*2c(Tr3a52e5&7vb7D2@#o73+J`16=t8y#fP;dYiCurM9>(G zXL1~)6v^6+`o+yF9mHh>Lf;?qKN4VAiI>c%w+^wmMyYv5A&K25$sUdsX_E+k|eG}Nfywmjzc=rR{idfsJs7bEWP%LhMi zD!?}K1;Ky$DNPC|Gh~X%Y9Z(`R+=9Z^Nt^ceMz?x^(E+#06z;zwc2A!_d4e_M|8kW zk*&kG)$U25p>PIbLKRy{F1;lfMsyN>7%&WR;@*UyMV>{-)fZ+lYm{gLK87WsJF2{DH%t6-4g#K?v<2T=`QisRDM{XS{}cIrU0#FHM#^y0*i0B6!B`}n08p) zHF32FDlkL8WlCM3k7v#dIDp`lU?WPTNHJxP0&r$yLrz^ETkNK=Mw6=A<5EW~K}#<%^IstDCnf^q_Uf6f$- zC4oecMT!hcPKVP=(g7qG6?N8>w9?n&{1o{buRmT^9z&&qUhli^i6~GS1W~}o4ti<) z32rYwV{*ycOd zRqQ+dLOz{{H1s^aGv;)_vw%ISUAUPlGjuFcSv7X9k(6yjh-*v_v>2N{NK7xK!LzT$ z(w7BM*SV{tAavXnPV>;w_nO$QiHGw?OLrP78xz9x=a#EwoF~<%Ec2VneR!Mbx}Z}| z)B*o!&`D>d186x#6gwm%0iiR3EbOds;>@E&-hfIIgh7Nu{nn%P*fQ#QlkMoG7Jn=L zJb$%(KD9@3RCyD?WdooHJmu~XS|E8^y(#TxO|zZJNCxpNK*wiNdb}fa2KNv(4!{R6|U^qUV1yJa`z2Zr6=j^hsuyB`>KKQQd7971RS z7@bfb7lVP5SIlYQ}p9Lu#w1E|xkMK)q|roIry`%a1yNOk+^PU2qExUNw4 zu4<^>SbaxhN$|u$t4s^?6O)926WC(za2!Pm)fM1= zUGplt9p0DDld9>bkc0-w=?vAhC-L7=8*X`VVp<+6Mdo6)&}=o z;dLN-v9m;h7KOzI;H#bwM`L~f1+IpgKQZ7*e%%Kw24{V9g(9GxahyW(6kAYjYja~& zam4Z*DDX87noiXE`q^m)*K`E1WaHEZB?9>>J%J;nQwc)F?RpKM;J{UNTV7YQmA!PH zup*|sG4@FX#osd-`o-k0Zmu)aI(3|b>lsWe1&TaJ8NgV`Pb-QW58hdq(g~bejt#QH=oy{vmkgG?n zkaK~SpsHBti0YC31S1XLs?P42d>|(l$K%bt=#=1_-fYyTh}w8XkqoJGPw6JEliW^l z2iWjpJ|(1&vv?H5%_ZuqQ>4o-YAJq0F^+btDRvt}DG4Lh4m?WkQ+`RfBV#{gIn>5} z*#qX?kBK`_PFIc=oGC4*vy9X6E@1PE`Dv~QiVMVbQn*z;Qf`lN2@MGW zh(%Yx1hnM}^52&WeF|C?2~p{6VhlarV=w3iwh|yh)G1Ew)<&y>>SwlYgnF0ZOBVc4 zUp{id_bN(o;LN!^d{nKwHZ-5xOrV#71VhkG38+zIHv*UT>XeS_2^{olYk875exZmW zU`ZgEAW8`DVZ3eJi}Fe7-dd`KxG@-Lkh~>^Ns-l1d07|VgJ22-A{cXXC9<7TLmG|e zQ;A^uMYh z2`l^c(Fiwy!g~N849l6P--?80J$M&m-x6U3R`ojNFPGG%9-R0>)8~jWvM-KYv|?(N zZwbBRLWYjKN+k(!4H)6=+it1do7gIqCfkeZOvmz!!-eLk1PdtO%8t?QC#w!DQgs&X zM+;*zH1mRix{}B0M1fPCGw|R&G`Vsb?U5N)hFaogV8N{`$R>;lf2rKzcXWj}Zmrpa z#Z^mme^(cmMk?06!H;YpA3y}#wpT?yVw8lC!j;7sj+!4|Z-Zg;@mj|3YiC}_FQlTj zMKq{SR0=xLeKXz%xZ78+N;GD><)hKf0=nq$x?)$qtiQSG*72bffSL$9a)vcak2gvO z*@`U>N}|pca-NEN=d=?J=@8-SGF!kTz7t_myuuHzet}$ky;}spW!Y&aq(~yl2`O*X2Zi zWcT(OLalm0ME$E=)R}y-Wp7$z#EF{a6;3y6O*qt|8@|2avR$V?A7NYJ!y%WNNV4Rs z@Lc$=Z{##lwlwiD!BSEq9pAZeMvcOww3}>@M+k6k5pZv0(^MSMMFj_OO-eGi zxA)vw-9URR@E|VKuDN#S`Vs4nAlZ{FayP@V9_s7gUTZH)H2J{bISUA$`o!hZ9YIofp`d4tuk{FSE#wav(wx@ zK%9Q8bCX7?J{Ijb9G|ymN&`S>+h6ZF{N_bQ^9)aTYFwcY+B76@iR7XvHW`QpC$H_a zQVkZ@ncbhymLjutgrYc)G8~F4HR2O^<7^y;O=9cPpo`aTFj~!WsZfbb!wWtd}_lwGm`{wb3OV7-nW-d5}rBL|` z>BAKA?nUkhZ^O6bzetzaNwJMj|?@+lSw9q*&7ZZQ0#vEyGcqv-ZwBA44@kX zaI4H>FM@YCzcj7JKBZ4gcbTCA0u-e-KJ& zaCS4TPX&-7ykkyC6i{!+Ed)X;+$p^l?USL1F6-^so-xlsZ5GL*BOm7nWPx5gz z|IxAyN=MH!UwyMNr9s1o(Cyt#rW_o+z*4Q~h-m%S)M-d#M`{@fQL}Wq%?4Mz@`aCz z)u%7IXRvN2IhTp|6ECMe+6m<|%#n3N3b4kE zAWGeMUdY{|)>__J#R85{t{FDX{33gu^|`o-5`#2z(*`u_KxMwTU{5kOqe0_t$}x}h z9s1(h>hepi#nsfP0e{I2{6&Kc%dMpwow8VC-rU3F^YTM~en|I{4BT=tr7S;VJ8fev za8n5hfTMl+@zV>7otA{!76zJjmNmb2PCe5QYBiYdTZrwh-&);VuTP(!L$$MB`9?N-2|pY?2N%^!NvbMh&qQB7~yJkQJ4bbd`Cf@R~%GIT7Y+V;0Iqww= z5M)VR8li+FE6t8n!TKuGhzClj|;-TAl37PNJW%4(q;4xb^borU$KD{R*6y^X9s z>1M5`&C?gWl4jzG2g$$cR^-WM?!RWdr4`lnApbt>w(-vAz4lY=GZM-gu|qpZ%lyqjAuIK$AaUNc{baF zU4aVuiIk|>3~j#YyM}2Gmt5-f+%{)tH0N2D+qe{6+jL|i>u##{LLt(DZi{(G3 zers3ccqBn!6wm8!NwYe!Lax)hka1Z?j7A2ew{FZ!owpJ*5mjF|dl5I-xF7N=wkKJ} z*C|(U&F+2mZvHwD{kAwv0f`_}#;$w8zMpIu#FVWbOR(p_U=JlRv2DqmyYnhve! z^+mZvDUl%Es%v6fyGrlHzT7|8y$Yh1J(SQv*$@FA(nSfRzPA|kYk_U=Zi{U=$B+xh z!_g>mg#Vfj-#th$2u?1+;QXe-Urz}}rtF?S9KT@=h*^gn)VB3fYuVjYK}P!qZli*1 zQ6A_o(U;f1uzCD5^-n+XV#cQ|J1~W_sy{p0R~vMZ31uaJqA%}1B+Wqm<1P|RLpf`n z%`^L_HZVAND0{N<##3A0c*@^+>bC1Vb}=$d329m0KSZ5lTDPy;?9|lJ#8Z6($Ajf~ z)0@=v(6FvZ_OIoKMPfRqsk10XUaqr9kwyG+its$eBecgm`{j$=Htw0dDy2Q?O*Tj) zdqwsz3W}3Gmuv8yQ(G6056?J26Cee0Uiqsm_N@uP{w+5s~ z{(Ftg!SEX{yd-NUs z2~B-NREH~{VwPv@{U(bk8Mt#GSXD1hMT^KO(MSQU3M0NV@CHCseDg$@+Tu(~cC#jx zSzba|a)Hyu}~TH zJ@OCuU2PF*2qu9|TCn4jtSAa22GkVP^SoG+pv;2^z>g!SHwEF_)$KDPX(2y{;RYi5S0RiY*r@cwd z!CN=tX1ubUs){)_`IYnwWmp_Mc?LQ6HQ3luZHh?F*k-zAuJE$4&NmfQA_&?&b%YnE z*H_`WvI1ThP(cMG2a`X9o=5|dDLjk;dJ&8e;~tal(dvoyX!Wk<#?3|<(w?Zciy8^x63 zNeCJgTOD>*{lRJp3?2_F?hsGMODl?wgSAqha&viOX+G5=oj=xu3g@8Na2}PK*x3yogypdh{_34h(ctv@AfNIgF@5;#da))eq-bbUe3s3fma8 zTpXKk4BHKyXUM%UgnN$vY-fgGKy49(A42fGo|9qBjmF1wEfnQnF~_BfX-&Vg3MsvJ=R#oP? zm$CA;xgZ)}2yE5YWqcfDcFB%&I z!jaJmQy=596CiI_dN0?xcKmko-H$KPCKZdi3snm5u&BD#n*^eo|HQ8A%~cf>N}T+I z>-p<;J@2OGbsELyp1!_pW=q@eBYw9}>7tYDGdSpSFx^r5@{Ouw>`zVxZA6W_i-xt& z_2qdUl%1k<&%{Laow2}W@jEe{MNCgjV9PMc_UO~uHcerT@_|DliJZ!y%M&%U-{JujQdf`f$+GAA`nR@U zDTeJY-pZ{D%fQe%FB;sRb$3na{+xwy(k+wM=jqKy?DCC_1zh^4Ucd`|hnBZELkm0@ zTs4Ll)b&A!c9z$M69UoIFV6?{ab^*aXD{{*j1;)h{bK(Bjd18FPL*tvH_s~U1%ASd z^$VU+h`nO#&lnL}ef>k7H7?A0+t5sgg?15=MrRUi0~?i)H1pj~9>aSx@Afw}dZHe; z#uVim035~30?p|-1R*|`wM95}ha)%#6@prI0gk@lP(9q#iRFu-x zjqIDJVc~>!rcvZ>!$Xyb@KW@1Ez`5#f3Wjvc&eu&!jt4n?PwvxAsOpjOK>MO zSy6k>EVY*xJ5M8OogK{;fQhOXV5DTTXj0dBa=%I@k@}8eq;z5jse&cYujC*Nfojq` z(e=szk2%jW+4dUN6sY*VnKW?XGa2@1(mwc~TXIp(`sphl;%0pK&UBxQz^kUV6--jS z)gTMz$mJjZ2mlp2saIUyc4R<1W+c z2jA)r=&5W$>8XTmAJRYn02h?qaRhXniY#TUJ}o00F2%X83lohJlyvNPXA9%TtRsco zxEK$vT>}I{!mh1ap=&J9-j%8um*S@HP$j<<-863YAzcHHI3r~3N?7Bxr_JL)Si(AF z?|4?dxr(iG4qCtsnHieeCKRZZ&+S<+x5KyHyO+cKd?~#rIyiZr0Uw1;91h_)*sj;~qFp!R<&cw)7mSs%*zSEIl41>Ap|qN_!&0c3UcJ$bs-& zIaK>H7;^PnI3av^KP&DC--pZ3jqut4D%Vk^(vYbl&h=ME!7!4ZA zyZ>IyfuXd?_}c0koq>8 zjqjOtfOoTRhx_+$qC^o6cjqg3EL}maU!y8xAThXfm`Hi!jq&NLRb-IVFzv4>2D(Y54fyM45evp%C0T7{lz(p%VdPug{tO@yOB3~f-FW=y6CB@#`lIzDjmIB*vi_g{5-}6k zaJ}=TO&sJH-%%@Xb(ZjneX^b+V2|ra0}xPDcRX2^yRFgS-eYf`vi4SGs6q%ktE9Jy zCI(Y~02XdG-uiQ(ic48& zuhaXAuWw-9B^cPS`pyo!`}!XH^s&-mKe5Zxo(_?8h4gr+#Cm(t=RvA%h1v+ zzSsp_PaqO?_tSs7%LYH}gdDCV~mAls-{$tOlD@>hZl zuwg}_(qLiU^f_8*3Ooij4u{g7m9#<3R5PsI$BZ*Dq$o8O!qn{O3rUrh^|JY5h{;D* z)}1LP2Rs2oiKK{@+2)3$6mtRRI={g?rMGL>{K2y+>5g#YhvQS1Od(ep4MZgK%L; zXcfxb1sCs2Rs=7OA&y{XFEdA&p)DP)MIFT)aLoY|r*)QWbEos>MNQaj)St%{PNKTo znS)T8j2SkB;((%o!Xn~{!?DVf66jnxVAQYBi+mG9h{{!2$X!)!;4c0`m=@y^b_kd8 z@OpU{o{@V!c@|8a`&Hk-gxNFUPJRx=*jZ?wb}1x!IQ=lCd}Y@X+h{A`I2MOn;oW5i-Oy!aoRamHmSg6(SUg^tZ$Hhpm$ia;Ekrb%e9>&E2B1ZS?iGRqhkiJv zfK!8ENe?s$a<;L9AF=T^WBMM-1Pfm>^Lq zx<&;f)%%P}IhEDTjv0)S*>KBY)Yc-U#;C_oN?#=b!aKY+mbSXp535dL=SX}@LQieP z6ef!>%d+8IS-rt|d!sM^#z`fgx80j2k{Y~dWA)-Kr05~By5-ibM0lyPi?eLG_&jj4 zeWmzxh$z&J;^X1=4Pq{f&y#D{*5=6_qA&oC-<6I3-It#T*`$^_@z{|Une_0|4N{nk zw}v)Q&LLIlo9ss4p%`p1gkY$RjD>&aUv4jq6q}>YSN^5G+QlrZWq(hTW_b_i2nqTU zp)0li&A!dPY;!wsI;+WFP3am0v>d5rw=DVM5Fd`#vVW(5rA!|lt7X5>hXeNE@mlup zHG_RPR?8+cVrBHy6SeFQ_~D>^_;fA%Lq6PVAO1C=8hjvhg~yK9vL}f@xz9d4SWJL;i_{ zyqaYX_y-p97yR^5|HMlEl20G=Pb}rH_;lDlJ;qWVBFy7K|HM-Mn%{oYKe3em#HZi# zPb}rX@M+LLv6TPHr-%F#OZjhn`fdNjQvQZdzvG`+%YWz7kbhz=|AS8t`zO}&Kl$`8 z?9=0{46X1pE}P21NNs5^T7S~r%v*~N9<1>KXq&e z9`xubfkFFIXL8^n`%?#U;J58h zoyLLRu|IVb2ZroVox_2L?N1%Tfq!9t>I4q_OZ)Th`SYGH^=1FNlG*f7z9kn*_{4wz zpy&TjU&eo2Q?6+CkK5hS`pKn@XO>&nI?As4Wgo{noF9vX(1=-<{c^jPyB^HzLy885 z;IW9qXGQ6_-2KvcFw)RXx%!F+-qFmXDY-r9&R`wOPb1)U= zimb=U^>9)&YePXS%?I z5swU9VfIvEploEZ8e!Sj-kwtO0=#Z>qr>*1=orS7Y6aJ`vKox!@ec6ETk?Y>nVe$X zp@`%3j*$`!B(02$GC8`cMZbPVtc){d%|nid)v1mp6RnE^&Ok{W`d#S|7|l0V_<&Y- zCrJ>M!UU$)Ss4b55mOeIpiDTpw}l_2dA+ELMMWbOk=58%Fh4MpWgf1ZQes)Uteki9}X*3 z8q}^e-O15+_wrg=s=a(_^!vMcIY3U+of}0Xj*$BNI^jB#Nthi+R{f(qTiq5 z|Cd*<)z1;{LkI~8@9DJdVk5RnCXR7<=*o(#1OLJmZ@eWajA)3HF=o3w`mlCTKqfxjBOD??sk053hFJ|}zKzbr zVwf3}qmGGO31Q)lUsUEQexJgxen~WB=ab0Z>Q(6F9Dpw!fIb35eRH|u0y}I}3gs7M z33ZP1vp=5F$t#x~%m!7gKrEy~{X1BW7SATuspO=Mhy!R z^S9GQiS{!Fk6!HcukcKGWpm}_)lDIb&1rv=y;|~)&jnIj@`tCxN!5)E!(^HcL_N!M zy|)Vk8)m!tkdJ0&Lt_fSqgs^WL7C2h8&r8#d8LX&4Z2(NB$R}Da(*enXLe$+!xT=+ z90;L{V6d;y;n#bU;Sz04+Z^SV0J$S+qq(z3R=xxPdoL%pQn?115#=Knt`&f!-i=dDX zY*2uCYjweUzBiRm$rjzPFalwH`o`V3gilBl#R`Een3t*n%Ddh~@{tI_R3-*0&@qu9_u-xe}Q4-RRk zuH;S(!)k|+iep~6^_1m}`a71nS-2J^7t0EUxOTo54LFJes8Ds~UDKMtCl~WxYKAfCQdydpusJ{T96AKkwqr|t~w)(F^ z-ttnTjH46nDdN138}IW}Z@gmPxWU0EWb{QBT3fFQU+G*MR*5nSZ(t4>rvmD6Ll*O4 z+rHA?xY+(1=W>Xz_b*okIx&5R7rM} z&nwBbrJ0oZzPK6Ms%)%2;20Ey#`@gS8jZc=MwNA!^Nu6N@Z*ju-dWUx+ zeiW2m;))fROv4AMC4CnA_K=mp?y4e;q)x(eOri*TVFWb8RjOpb_q|?Or*#W?pQ?(a=PN^CQ$c^3nOD+qPklErw4ZSIBu8qZ8+pNk^S@bT{;`sb}5F)K_~c zQ$e%WKc_0VdW`-%7N}sIjgr6LxI@8K|l1?p=Jv%rzmCvb04W;HyN$^_PsD55;*#&!PL`GgcTThr8 z>^RiPNC6AzPn6;$hJ|im=jTo<)D1#RZyWrlouvg#RBeiugOe7=q_7_HBG5BQYBLqp zeshH{eP3M&W7D=JrbLRZMPj3D#i+l@?&r2_)M&ILW@Paw-Icv<1f`|;wbTW-kv))W z_&c)hq&00^r?`Q@Y)v51cTs#RtnMK&< z&TkDw<#r^i%q;`OCvdt9nHSE)8&BRDgCm`oM~n62l65N zx4phSe;!lXlh2zBpNH2eWZfy%5x)2AnP;qyzF-k`?CfWDOSmYMH-lS9aslfW5K6>5 zq8U(abGd_<>s2_~j82U5V5kogE>);gipf%zRD#QCJ|S25MCg?GS)JzT`*Qy_K& z*ZA&{Jze*C+e~|%5Ol39iH2I0D@)%7yzQbapE@DIL%`@zW6nH@04B#Q=Q*tT`*owkBr4Soffehcr?c2*o{AIJ|H@2&YUCu>8 z`7k(=g7CGem6c71AUJ1VX>S4GaSr-Q?*9DUlC4^ZZdDQy(U?)ri80rz@_osyJqZ%& z9S2je*)5UPyhH+ki8=5Sr3hAVG@6y%Ov)_@ebFlC0%tCtiwIZ8VAsHh!%<+Qts0oa zuX_gVWwUt_t`=O2sXN`y{ZgSJ>k9OX68wE@Xo7Z^cQB{wUYkq0WM`F-pC*rH0WU_e z^#^G}Vd53*Y}paC2_to^yMbk1rRWL+gEbuuO*=W&ACxN7rM__ykSYXV!~4zdDcyI2AvX}YEj#BF5qazk7d^2doQa50p##y^ zRK9MC&v2bUdUg`TuC$I)K7bqCG$Qv_JE}SIZmf>f z6SZVDZ(=P*~LX%RXq7mb=WX33RWuF{3X{U0?J- z0;AuQ;N9Ud9Ac{wUO2b{c6szh{o3j`V%c52>P{Q^ph-GXt^7Fq{!-F6x1*%RRsdl^ zOyA=9gkGYiCB$f^(?V0=gTPZEdGGF)fQfPc@Imh86lxGgVwkg+e+iH%xFV&pY zU%QB@6bVyCEDmFRb@SSF6F6-#2t`3%ejkQLPeZl@=8e^#fH@XA2`Zb$=wZX~Z;v#J z%q8u&SU|aJndNVXr3DYqmNz^=g{ZTnorZ0aQ{7u2exVr$xj3vFojyUT6)pQ zkWh8YB#mRb#_4PNB`#A%ZF_li2m9&UbJS4%7Kb`^G0!aPK4MPY}-lRbyq zD=oo>ju2bZ{h}4Gs=7s^$0E^M3mePCihqCiH@(*-{~ln|$N_AhD9`cLP`TNGC3J=F9Fm@j`yeCMj+KYI^AF=-JSMD<}eRyB$h1Ab&R^{H0?z!@j_Z zsroWCV?_YsUUd)gpYkQw!1YhTKHUN}PP(zSf!pw6Og#Ti9j=85T9k{sq0B*b#-jSt z;wI)hwVbY7Tw15`f~HRh640{XrEw8v?lXQ_tU!0osWpL%wg$)-OEbQFDX&M&kYp?^ zAUv(`zqC?R8nhK-yzHs$0pgdHYgiY8f)=2H^&l+CqwC6eyk+s`xj+}XD(2o}wH$k} zOE3LfK&3tYs9rd})RUyNqwTjd?W2^g2id%hWqjHJe^@!K-blW|%vie3?qxX;yexSW?(uc(XX|D{YK&;TeOq zb#?K!EhXHk5W&&Lak~y_WAC5y)s8$8Ofs$dCmhv3p*xV4P{xBQMG*V5lJapIw-(Dt zWmh;M&?EUAXSX{cw-OH@h2~;;#$rpfw!Fe>5E2HqGQZs2c^QSOvdwsS14VF2qRH(TLg@CiQfy;-zb3qj%vP!)%{;C|5+Y3St*a!6*-eaQds3 znGQL^KCC54VG}$@lz?J~$Ma^vQItAxLrlRSkBb$d(pr-=E3~^Fh&AO=7G%&e%7{g) zia^%5Ev#Tym)@bLEm^Pe%DxI{Ub!_`7p>6wmFX1c7htokEPHV50mTRkGGeo(1pRng zJC@6EIw6&C`hxBB;v#y_7%IzcqD(Bdc|Wh`ppl@2mHBKna(W=KN#Db zizohJ1_f2Zv26tiypMRGL^6KXdxdcM?eCPCe3v$=cwptr!omU`#0~eL2`1jz9?NIZ znCz0~vU|r~$SWczOPqe7FiLaAA+ps`wszmjY@^FfK+OxDPL{e zm)3YK_3lBB@a?5Fid2C!6^95B^-aa9G!-*3eZ`w{P1GNQC=pFD#vh-zbmCu-^^32? zzMZIt5u=HDtbQ_9UZ*F^OT6|4*}_=tlmq9*_xyDq>ki&82fB>tFy;w0Y15D8c2(o@ z$(@doF3X|XS;WUX%(j2d6kw|j*~Li^hPbs_a>Ab?xS77wcC?N9IjQFV3=aPBc<_&h zgMW8#2R~;pi0A(EJsx}Y?ed{dIt1rqs9^u?Prly4Gq*@U-h&78ZwHK3>7jD==hI_q zMOU&~(Whg2Fn$IQqWTd;9YhG)8( z;awslrs-ShNv8tWi6+1lnTrvyVNsbq@z{(41yzf9D9fQxD6HGY{xwGIW05o-3#9Sr z`$X)EhEkDMKJk9hx;k7LsV8r=Nw=L(7rZ~8jDH{XI=-=TL;C))%J;Ro7xQx(!)zKl z&oUu`V5|~^06#`EmBC*>dgglfqu>K6Lr!!Mq;S%Cyy*3%Yu6c-$c0dGgv?Lsc*yQ7 zr<$wv*1=jarX^BkEHhUBu$LU^UdStiD_JoSp)qH>svTi~rp08R;TeWDnnwVkWj66Y zq$VklD?a;TE`=G(LPvx*D7TfVT#GjGB+aUzAMyV%u>uzCNbooX9yK<$xpHH5kvg$T zjr)a@X@V-7@qutq^{1Xn?}RgIvSt>=f53coe07bWw)aQou|$P-%~~a=Nnh%;qFx6e zVQ)KV;4iQMjj+)K6T_7D;-b#j#x#Cp)?v9vfWoq&Tmd67y!9oD9a7!q2?<@*a`&a_p;$z)EovBAg=VKucn_rZT?J$1%*JAX?8s+a9$!w1<=xL3` z`uw~3*2YM{pfEWOtoYDljqM(~Q@4?EfN&TAsaTcQv<{k$2d2j*NW3s?Z4dyJRv(eC z>(vgn3=VYbyLVHIdvJ>ucO(!RsMgihvFDvYN!aU;g~{^4v67gVAIjkA4%%|@vSFN= zehB4nk5>_iZk?r}s1$f`;6#jD2!LJRqE-$jm>kLv$t}3mVVWN&4*p6zI8>Jw9zK}A z7V1w%c+yMSysheZSXzUXl($QZrJz4QQ+ixCSryhilHubA^I3IIl(u_*{S5V~&tIkD zBqb+nYg1GRy+wC8mH&(bKvFhZotGMeZpQfk*?Skz%CGC*|M%0Jk&Y6%mG7P0E8jc* z-{W-L%D$QtjU(rdC*v~mXx!1g^U%y_9J{g6IU}8!QLR^Wjy$81KmrLQkbnaYB#=M~ z4J6P&0x2}mKne{ckU|0}q~JmWEwqqA3N56N!2k1IYwzFVJTw}6?DXNFc%*aoZ~yjQ zd+qgp;F%b(^h1U~k&RSd2?h*VWuJ6jBv`PSFdRSF3caBqPbI{9v zg>V|GJbNWlGRUo#?R%LHh93K<(zTNVmD>>@;hDJe9!Qvp_lt2#2(=I^ImZH zwi_=@daJ84G|KJu?j<38aJVwz6b92*`gm`_%g4Ro{6I(Tqgf25OX_0TgE4)hZv`R(4Yt;s|7 z`B*xR9JMxB2Rr4;n+;x4&EZENcY%UJFc&oWA|H#i$j-=;J}7EFt$cAYH>>lW1Lp7(7I@@ zwcDy(PBY&^7VCwZKqag&fFckg3v`1UIpWGzYkBp$`+Qor@g#@YbQ%&QkndasM@WE< znqAYu0;mL2@Ch-e5V!A+#(&-76LA1$(QnX9Y zK?}~5o8SORqb0g@Bs-2wk2Ff1Ek?8EkF&ZL(^mp=Hd zLq8sh!zRNg!!IEf{veaf2$V9gSl#VXzeJ=)8?)0Zm_k|{tNLraTSI8Q1j3;oG+nvS~z5P{I2;_ z_1gR7N4wFYN(e7g1*}8A-`d_Raj1KVGt#;aDIexds0&fyfe4e-Q``fq(8FR}4Zx2}n^9$K6ruxd-&VmBEB!plg?%a_Md~ zJj+@xyH#v1suLM+T=AM1BMo%UkuRgiLFS0Z6KMlG;quAW#xb_er?SR`U579>d!o5! zO{?=LJ&6MLQ6$&YI&LgP8LE-n>N=aZjf&eIV~)c0HIj$jbzQfen1aY%pOuTWNw^e{ z4y6w04XMmuFdQgHV^%=-tSpE`GKE@d6E!Aiy^xxr*;9gQldbxVx|{GCJ48;P^PV_y zt=U<=Nok-Ou3UwP;%xpce9fVKl7DOAeL(UWgl3n-#dhbE>Z|JX&EG!Vcte@crSxec zDm+E2)|iWtwbs|mKq0Xt8aOTdC;-MF0lzE&VanxE0Q6Y_D4-d!Qqiz!pXMQH_2h5; z<9bSgXF5b*PcanDbB>FM(rgvp?!!jmE+brEF%wxQrc~BulGs?*qeYDZbmwhP7o%?+tvTCY-UN)Bb2sr%Q)YT?Y#T3 zXaG*IysarL74!|6gjna`>F-B8)uEUJN@#?986>V0G(IoDb5gSNYl$f>n_n(48AqzJu2RG-D_a_lrJuy$RL%slfY8Os zK{)4aP7WfuvPmN$S%ePsBPD@=+SH1^c1m8DyVL#Y5iiD)PJST`( zq%;>cu_Oj59Ac9Z-ymmLK|L=l7@JH(9izOFA@$E9`}8n};*M-ZB3^@WkvAvPnbZ7!mS4>xN|mgG>B7!6G6viyq(L4+ZJ2S?0WNP=eSY#=sG)32g2rI4Q z+mQ}IR((-?6Rs$=VG1NHu2q~_CGC{3H7%HDg3ar;oV)?1wiV$x<`H5T`rEIHN(M7lokmqI0fxkwOutZadIzC*~lX4DX*-Wu}A$?$koo--HJoT8-@YA zAzs^;+OOu5i)2k!w%0^hO{eSGVp)^LU*tk4Cb(diGM$>LVWhyxmV!nc`)8zS*Dk!J z%L?I9l&DRxZ2wtE=uM-+c#$zU>tX}`QV?2jx zaeH*UJ8ps=ne9h6Eg1oh3;vZ(%oQC>X3 z^fWLhAdd%=#oIYW!MmOIlcn5Fx9m2`N?~^gMFcQ{pv@whLG329w(uctZqM*C55Pgc zevLQ&s;oE}?7}5;s1{VGaDdU(!+W#kgt`S}%JFG-B`w9g7yc#rY;Qus#sV>L0IbC~ zk~V@qB=UixsPYZF9&6Zz>1@T0npN?m3*CJtd?uR1x6xA3(7mLNEf3m$E~R?Sz-Jkg zJt+>OqIA*doZUG3J1XKXT0S5lKPninA$#}%DDUstRpliXt=#*fgAay))nNFyy=9wM zYrh;Jm4RkRoV$n%4gf3?;hrr;G9+^u={!w}{e?thsRwYZ;D?qaIGT$DG0}*+8D;N+ zG|P8az(z+Yd>y=|aZCk4!1$ZC8?m~g%)y~IG)UQRTne9hyo__YY3ppJ1$a5k*~aU5ImKuW^a9j zxCCQkjVoq46Bx34e1>5Ag5*-Kg+a@rC`M3a1-p{VHW8r%b-&hF+t~0%taQJb4yrOVqZeFQRhefIz zrOKBs0^R_eaDy9yggehdDLTap!z2(I?8K<{LPtGChjA9TP(zP&v9kft&n$`$aDK8e zQf3E~kr=YJdc7q|W9+WLpxACk(k0Z7W?-Dpy82|)M@)g!}W z9mGP7xSHoG0J2-)5EYkH7}&EqnrA6IW$YXv4HaY{D>`7>YbN@X_`D~Pi%NT(g1KVxpIN20a#7hqM!P^f%U-3ncoGP_gOyskWMwoxSoS%QOu2te zRK5^W@fKB96_luKi93~amXG$=f25fN6=$nJLH(m#alr_2$N!Z`*gFc!!@g(|+2#uO zipoULC8YSMn1X2zuVbDf$-9@Hhhrtr15~fu^Ki*M504MXC>kduX?gB_+tS0g|6i-M zn%P<-5buYBH3QrUMNtd)iO$?QYMX$yN0VJQ|>jZzxOFUBBD>A9|o&ETa~W%hXD zdz`ai&>=!EK5X_nQ*(=^hG@<=r8y|u**qs{09M}dxJ1Lr^f0lEXZbBjE3clW9}4zo zTB49a5Eh?rJ+l(gOw3ejsjdLGeku$ufU|q@q4C-DvQNo2;;JNx64|=hRGN5jMuea^ ztc2<#PI##-DvBuT24KGv2n%Vu$9;`JofTQDum?DNc}+Y(ERnpNeHs_EH-w{kB5f+f zmojRYx-9ZhebhsMqym^^G79zhqFFNAkrwT+CmsOz4@L6NX3*RNKnKSDG(ca_iP55F z`45{4zJd5ARXYyp{AcH{)XrbMvRLEq+EZt0r)STdJ2O|ic!B6cPPS1yfAR8}+T#5A zGgoShXJ`4i`@kCYdwJQP%rBs9HyzZv;sqoTFsaNV+#EOTrF5%}B0>3(S$)2r7EAF= z)#h=lOs!C~a(i`44yEEHonBm!v5oj}FD&6Ry+*O*Qkqq-YtWAVG7h2b&fYqGb|*X- zG`e9xmMnJdQrehncA8-xqAQ<*(s+fo7Ik_sYq@WaLus}*H)LGTg9kGNrOcPQ-e=~5 z6-bFSoB^dRg4E+_d z{dhE3`Lvbvi2t&*xoj zv}UfYI4as~#9;xf%%s<-T%asvtEl;9eWL3hV}CP>9G}eNcK+x*<6!g;n@NWr16+l&va4#Dpu_Iow5;i z9RhY_9bv=UMT8?@ms*7wM98yw()oLFGJX#S*>?_ODePtke95wJA|O}W$I|sC_0g3G zAcuxx^Hp~fjFe}{T`@L=;ZojBD$Zcn94u&i)z4;>KYYQ_hZjcML>6vqRvpNZZ%|t& zJQCRhz=v#6Vh#8de)W5s*M#$A&{&8X2290J>{=)t^oNv5F}@wr46@Z>s;Bab@um&B zk*WokRUBpp3JWV_&I(-m3m#7ci?y3285}C)jGnyD9Y8V38^m^_l?iFUd$9m|_eSwc z^O|yn>yHpd{WMS{dDAa(0ECVl!2+4V+zFJo=F%Dtm<5(C1d7B|?~JGL&XDtDt8X{c z__W7dKmyixC>w0$HiZH;@p(Jr{HidWoS`eGe>*@|@L{Zwbc73W8A`)Ko=5>vsEM_; zsxp$6ZWfjV?GFyukn@x;HatU9b2H!t+J8sNah&kQ)>dafx2sZn1rG&-byyjYC^{@} zpTZKt{ij=HAF>yW33eSLfCcz%S67UCEsLkRRkqyMFl@-|b9G&B_U4Vt>*OPao zi-0<+_JQ=|I=^9Bbf6ai(#;$bg#h{Kzz<=jPdU?@pVJjI;CO?O!cqgvEqoGpRS*xn z5}`}H@x<=Hs;0e4xvyp~Vm^H2r>ECK`SE}b&L0)lAm~GjeU?x&!xsSRk=bx}k^BN( zaie7srM@@#Y(IBKK`-uo=WUNnK2bE@8Cw_A2@*!UWVKm{jMgpQt299Vgs73r$@PR@ zOIX~YA0}7q`$f!%1U5cU5Fnv`IrsSPMMWk^6e+VoBd=aWz%8Eny-z;57nb?xJHly! zbR~k}!d+cz-QHT=M)Kd|%1&ToX>Pw*kov2We{{xXF#!1#Cd=tw2z!jLMo8YXOSb3s zeOq;n@%3`QC=x-X`As=eRDWul4)*+YM-bghRcv;6Y%$bOS-!ct7U15ckXe1>!>3f! zqA-ZoKc@&k1mWUg^%1qB#x@-){d$trKy~fh^Ru)Y4lGaWIqDaN3Z#p#l@R(GUyY<9 z2Y-$BuWZx*u`($-(q=VIkCmAZP$cKzwp;#ZCE5yNLd^*=Mh}D97Rw z{V2WH1*iBn?x(>qY%bDOi>E1%L_H^-LlJgscF(80sJdNfNg3Cb16)t;ORviAW%HY% z1P^&vU%G(v{$gJUr_2Q*N5KW6FZ-?}`nlG(QhR9?xic7Z*c9RzW{vBsH$MCRP*(lK z3Bp}w?K&Bho-)32TK}a|69v@g-2`Tg3eu6q8)*juutZ(rTZ#~7m^YQQoxBr-Xsx`n zo>&g2Lnp^KE|KYUnxqUlNhF@;*gd(&;MivCU*kGPh4mSWDA9B7{FGfu%gi*xR~cN; zK*b=r)$p6l@(?&P!ck#E^={>Cd>{Abq|K0Tv`2^CuM*dF@lJj*+C!oYrVR)Et+Z;8 zYX4U*PS$-4sw}@y!u`Tc3n*##j1+X%VX8=R6{b$X&ko(%+-o;pt^x#Lt)?%lHk}!=se*4uT4jLL;_D3!)Xb?gxwKdD; zr=LG<1T&f%Lt3+ClynL0(gC<0Wv-N}(6LOwMAqLeftKXa*&I!cD9h%<2iSIsz{#$J z-rc<($We$d`mR%aO}J{+>qz;;8w_Pq?XegB9Lr`uTVdff8Cv(l%rw1XVcguq5;n7>kK1>AviMmARMQ3|N8@h=VCKHzVS3TZEPsKFO># z1FV4EHDK>j_YG7tVi7*d>{l0)N@seHMfB11bP>t+6!*)rhoOy0==bkOLJy@z7ECtl z5<%Wf415Loyl}S2=uhQmA65JMS+{x?@$_DZDhE$tp16~dw1k~*U@n_Ve=7XEgOS;@ zdiuyAEI{mAvVh90K~0p)>VDon{ClcK(U=XD$&=vh13JMr!_C2ji8fc%3}bV$B2*zm z3b`;Ri{*naQj%$1)GJ~qXI3O0xTbWVZsHzICBGC+YoJRR!NPd=x)kS)hyK~f>*PXI zY*7sFA={~F{N9gXA2B0H_B}-RY>(}{p8PDB!whx$;Y7nVDze5R$&M>tZPV!^hUB6r z-*KiAwrWCQlGzJuzj@oE5Zbjyc~lLXkPprhZ%;;59wG|Bn$S%*K)z48S+iGH=5s2yY#m>JR*f&TWy6mCjRPA?=t*~r~+7ifP$xE`yWC})7G_J z2UZD2Qw9gX^xi^MX}c1^8TTw*>uXmRpFVp0s3&o*)UIAWSO0rn$1%y?%*2r>x^X-2 zo<)s4yh(wt+6q>;>PaVbbi2^HMZ_9uIM)lvU-C44_b8R@W8^dKW-d-%&htz!HwwiZ z;@;~oE27v|D#VbcuW&;Bj^04QRN={Q5e+?e?XssnHu#s%+K?j!(6t*KeHpVEd%i6O z8&5&9wxK8#V%Vyr-QlC@xl3TG4KOZaZHQL4$PYH`^{}e)bzluzv0L=)WP&(q#)s9d zE%xEYhMXPy2|9%Wts`yj^`**Pp~Jd^P^IRLu%S|m^Kf*vRS8kn*U1uFEl_ioS|G9C zqeZRymL&PO;X^heJBwJ8Ds<%I$cm((QJd7>Q7+HLwCDzKU82M<8NSrL0^{t3j&i;f zf?I-FhU5=)TCO_?ADk?H{Y~%^fnI4r1whCUG~*cnb9W$ zuig-G&CW`X6-4dqD$LK-lTQXjUsPPCA%fc64o3Hd9(1Rf>5qlbb0)zmWZ%}A7cmh- zKWp)_e)qJi)!S?mr*4koEWq$P*mS~mZIB7J7p7Bc!@|nqo*G5mKMW*vHnbk{OSNwb zP7)H2+OhPD=X>^Nzawj#AD#|Ve8MRguY0eBr5DI{rng#Hx++b-4jk`B)hJIjx7J(h z*KlSAWD}vlx7k+pYvO_a9T8viSZ45#WH8UbuP?WbtAiTKdOb) zcfb<_EM6f1WvAnX?U0UjUyO6y4Ifa^?QTXuGW_Bln1Lud_s#EiRxd$RLTjt*;zLv} zPBPC4kq3I#Ntp1%ZP4p+SqC028&gqQ!RI5wj|PGS1;{O&JGp(6*LIIuVvF3-ohI7W zxz@&w&P}znf{!|4yUFbGeNBi3H?zEJRWR zv0(r3QqCXbRb#S@!J5jwvPruGPDHH#gMXj#O6~s#=YLr7ajxZ-VZjlkJ^r>#Lomoed9+sHW4~ z&09q}02+jM{2=y?YgbiY=J~Wa(n~mJQ zveDea$kMq&rKu!2{)boWtsH%PktxAC|G1EvEx4Y1R}k+t0KkrQjVm6Imp9AbWSVDn3+7%heRgZ*>JHc1Qzs zsOit3AZ%@JVi#6FLT%#FM=7<}Tzm9UbU(_w*UntO?jaeTL&L1=Es75)0|@J%CCvma zB)n%6MMN<#{vKbQ8SOQU9axf5=xr#Yk@x7XO}1CpS1}o!VpTl7x}g$m8y$=h&!vml z`AK<|Pf47U96@zDq(xx1S+izzZW2sNinZ;H#JzVp1AQcbsI&;8AkGsGvO$SVSgSUk z&6NWv*i^EO>He*5*q{7sYq`}VwWyWD=<{1U9cyFGlte@1``m6>z6da*r6ZAye}fk+ znvW35!`HlkNR&;oPwc$B5+Wbw_)uR%3tm!v&-I;kaKE#SL8!Gtk9H8ElAgk`L9x;e z;!^#ikBp>?p<3ygaKEl-DENNpYgbxIbwtw-Dy7te2G>G!UyW1Sa54~oJQs_O21`Z6 zUhvEE;>-~zO8K;`^A}_0;?E4D4TlXekd0J39|YXIeqDAeHWWb?)&UNLI+7jj78MN* zaASFQDI*b1cd2>OMkmBR-Ts&^ZTMZ6d)CGD`IX;JhHB5HzSHcCIX#^@8SFdC?=-g~ ziDU}uw!AG@otM|Fm63#I-7zOqI(a=S5P#fmu~r*lyAVj%i4(R|z;-6}{e#qYTi8B( zGLv2xsN5cDgY1&%Ek;U&ca1X<2+Y}J&H~2rwZcRqnQ$df`S49L0j;AIxQd6%kc-?y zyZSl?;SPS=&P|XQNtMfWDJ?$bhlcpoqIst-HG8L9^ak~YG1o0O)#pm9jK?DPoeGAlU7b zEc=rp%xcI|2Kg4%5-`x3@NbAj1%PXCDp1HV#QY%qX;;%uAb^L@JY)+a!pef77gPHs z3X3E`IU^68TlD+cEXtU)p)v$y;`V3fZ{t+0CTlx`whw~O=s#)3qK6DRUJ^MbHH z^n?t!&MsLZHOOTF?QDKqGsq-GY6HKWII$`G)z1C z?EEhcrbHfQgNu~PB^o8MspJaGGO$4@~)^Ng4 zQW^VYx;o4^t2XY1;q~E!pJez0!^sDR$7b4FOU-R$08H|$%abTT6w(Jn+Y<||?W@P$ z!vx#}PE)_?2!?it6Mk&o4{6>H@yCH77Q_p5E<+z1PWZ6_pU{9$Xuts3}4JZ8AfPc_{f6#!p`+$GcfPWfJ_^|>1q5=P+0b@SkUp3&D z!wEk&ARS>q$_@_P=L3XzL!%=JKQ`b#4Y*GOe$5968He66lJH{#9?*aXG~m~LfDm-( zp^=0i8}P6OJgfoZK0ru2baW))#|9kJfMXhPzYh>14?QuG@M8m>)PN^7;J^3)A@|Uk zk%S)`Fs}jg8t`9zfDnG@(n!LO4Y;ZSS2f^}56~qTdSN8t#|AVtps4}x@Bu9iSRG0D zu>tEEu&x3B%?Ery19nCder&+52JC9UfA;|&8rdIR9H_4m^WdB)wp~mQ2fh}3zx?5;eu?v_Ln)>;#; zd~J1w&3nm}@26c^U_?H9h3v=-W?o*nqdaN zO%aMU=Ix(ulhR659J7=p0LT*%Ao}$RBm&k2C2|?1aL4Z2Y7|QYT-Pbi#l6%t$(g zI6%u5Vdq*hH1V=@UH{U9Cgh$lt(71IMBAey?u&HA{&khuc)|EeVnnksII3dgWF1SowcCBdHtCjIINo9 zN;u#l880QOtD1}>yITl0qXZ{t@8~1JnqQhFBOPeb~M81G5&tPdP0Y!13hWOZDY5u*BidjU=BN87#Dx zuoTJl2!iW-P%geOGIlB$fUh-gw3ld{T4HLk7+m$xc8dX(bQ0ZNHcAd}aNy$Pb7}N{ zJ2I+7S0jJ2k==eMqZom;Mhj**ET=@Kqb*XWpOTN*m||P&H4G$PaR`EpSISj;djoF1 zfbl|y);7#X3M;20#)H8DBcay-XezB-#uT}=cCch_bjFpgLusm6&lr-a#Z5~h;b4xN zPMd_c`}HZ>9`a4aJz1}4hyfZd_Q{w{Z&+*11fod-EGmJ7g$ms=>#FR8xlQ9#!_>%y zVBvz+{RqxmnqXyYtllt?11QjUtbH;rh2Y|om`G`by@bh`$`PB_E9qejh&SO_ z1CKMU{ouTafUQo7zQRc7R{*Naz(Cr>3Z@gfH^OWz5YsXH>9zI5M01@_ylABuJRLx^ z)&@f%Fmq8=R6=gVby7vF&4^6vK*ZAVq|({Otj9VSKJ|m6 zMhzO9Zb3EPOVk9&S-9f zLX?;z?W?xB>`ctuS!fA|$rA9jt=@&i9(KY>Y#=2}{08|bomTSwM=1@EJzqS3y2xem zXb1x+;MhKGwLk8DMyM}v+hRD&? zjmQc!s6s2E+w1duxWh4-s^E4}(R~z^_cyZLsi4V|wjr4bZA(GkQ5>0ysQ@cHKAS4z zvm8Suc#Fsk(6oTJFuBa_YF%9q)v!FS0_p8)I%}ATWD<^Bn{z`uC|h;8R8YUQ;Ep)a?rYNLF+P@DA@cg|trpW`gTCUBjbr5LdD zY(WzJGA)@T4x=#pg)~Vj61G4&BKH+$sWG8>wbyVGvDQiAnCfzcp%NSkz{TVHN54T_ zS)4K$Bg)TaJYD`ZQPV&r@q&24Dh;(skFH(xJ{?FxC!Nfb;o1y-+zs=7~ZxeL&s^H`$tLkdkO7q9f z5)tn^&z7FEouGSLE5I;_6(Kx0E+4KrH=@$cZ}b2z2OE2dM-kwzx8%^Vx{-Wzwj1vr zIgxza_ni+Oyt#Z#X%oIfqwef4-}$~b7y0yqshfM)zCh5~lv;*Dxw9cVi3rg0&7BQ0 zQ8_rjTF%?F8hd>}v#A%-=;+bMOJaa4iXZWZ2*=5IpKZx7q2u-RYRE)HE6{nY`>irY zK@g2P?zudN2c{)N11C{{MKAIo?zPny)yY;q+MBI!;0sr{$7$Ob{qmHfms0^zJng%^ zi$1$vf42X2s%ftzmY&5DwPemeQLHxc5baR*1!LTLKl@M-t~h3p;CswOpSOh{Q)b$aleSH@MdFV4gTvRE3Fwe&-Dg8YlB^0DjV_;#`6r(Tc91;A(Rnk+ z)B0RobcTa>!FdKB<-v1T)At4J4p%}8%vnkIg%=V-zM>eCi&&szydARmo^>#QxUS>9 z8LZ*UJroWBw!~I4Dwm9Q(Obx6AUp`7G+B%sB3QQAM=x1B7Usyu2=OzT9_QdVg}i)a zT4Uq_K@r%wW~8T0ee^N|c^$ke`D_m)ayS%dee(C+v|FnT+X)cx)%R{bQ;vS>G&ep5 zq)JdI5ceqP1wexm6VkjoE%ZOv>$)YPbbK3TJXoYU5>(L5JbZeA0If^@LcD# zEe3wBpeG8&wr-em8#pLIj$^YqkYGtnJJgll|{f7G*c-Kc75P6VGeO31B$3` z5G0JFVzk7ZgbsjMWw8|J3B`m+L=b6*}5R1YS%@+_V5Fq$<}pFH=9ded2@-eW8Az7M~sY z;Ageu()0Ni@H|8ew7S_O=nKphqz$Vrl&#mCW9 zCuH~euQq(ip7mpdi z__2EQHrl~u%QT@HA;8kc#PBX$Ttp%1kW6ecJ^fd%x%k!Cd=YBAL?o)lHVDCm)jR>^ z)4>O7p{*8QY(QZ4oQ(x7F&f-;;|egXPwPl|eUs?sX5UlQ;_TZ(zU=7uhx1d7-*+8s zf4lKM5d98to-SUeDJ>t^QckyhD4pTzF3HkD<&}+ji#Me&T~z7!^+Mt6B@V^r>7C|nle6j7Ed`hhpILE;EO4i` z?rymFazfft+%gmpS$qf+c$s`0x(rpQCna|LMM!!HMk47t$@eQ_`TqjBrvL^vy03+S z1rU{no;DF64F0GFM;H}`HE)X!w3+S?!-xQ=o-yamIINL>TOOIgY|tYMt!=%8C#SvM z@sj8u8dx9dEqR5nH#=W1Vn&&Qr!yLqk=eaQ&S|6}YZRLH82$Fcop8W9Ojw%Pzi(IgYMDS~(t zvdLTO4LtR4<4yZYE_4sHGa!6U;bsb>2j&Ar+4sTn; z2%eP?C2U1Kj%RzP>TF%4C3}QY{Z|g6Rp&DdTa18XzsM~QE>%b_C9laZ;%yD zOa+405|#^*DJ7j*pwysQhbljavm#N_0!PtO5>*J_atpQMFuZq}2-J$DT-E3FBJme{ znfflq7g!ATfy%3KRt6Q9(%nOUN_inSVu+VxX$;;qe>mU`x`RQ|j;@504r zLYCDYGd>$o_venythu)2(&&ngdwz?)w-9Ys|>3cisKU<r)SAz;@SEi7U#`Zuyk#g1o3s0QX-JbknP^i{gprI-cCM` z91Q04RydZ=PO%Zy+4oD^*OxHo z@2mm)rt0~W-|^onp8P4QmcJy7)YLfrgErE*tO=jF{PcO7ju1h6E4721Dp(IwY#c;k zaWx!yg|s_cGE*&C4j^|26Kx4v|GjclWWg1qh`vm+u1|t$L|Cvj?t&!HTG-~xO+GfU zY=T_NR^4Qmp4aw>027r~?^_)QObB4luYC>1#IyzpdA51tn%eYaSuZ4{ESf*?TjHFw1mt= z#4ol3d+B;F$m(n@jw~V7;mX0@at0HQh% zLBaR87PPVduW~7WbC2^ymq%yqlSGXiU#*Hq7?0DobntOiVL^U%Z2`oOu5KyId*B-W zSGBp1HDvSjTZS_}Eo9Jw>_XXs!V+C+Xp)JE38i!zwaSC}dLlW=PbE8f6nZKU5vB}D z35OETBa#wR-+JEa*RF@4JXAc3_}(%>$==O7)wEhB@jEW!^BFRTpkx9O5yC3wt%h@k zL}jY}&dU5=KnTw|D#i)8b=mB)_r$eH>NClt{>7f91R;9Qwfsiq{A*o{Pv3trAI%n1 zM2Ue^=}6_t-c@8&DWkN`am;jO7Bt!05n?<@y^JP(z`;teJtOt(aWoHC2=DD?cR7B> zhxdIpx-u@%8Ky=g2`3Y$>yIYCxi^}`hx@Lj@7dU5KAWA4786f|E$%_Df2&ACSknQ?wgi^d)#%u zt(UF?b}F(-`wO}CPO4r`$FE_;Ngjb_obfKwSTSI~#(5W>d76lt*3tMZMc)y26$mA2 zetortU3F!XcwESG@XjM}6}s}dilo<*+o_iqI96Fjden)QO`JnJaIVZ+TVi0f?&a0| zCAy{Ct=}^}wf?eD0E>(jVF?ne)TL+uGkG|Y?QY%Te~(`+9EML4BAy4KFdT^Ur?%M7aQn6IUHVHdcoW;(wi~v(rqm7}w3lN0ZQgy`J5M>Ban1nL{ zq4Kx#Rqj%%0d<7$c75(D>a(Jy5NFIvbWgLHzPYzibfQf`($-3kH8i=GChc$Sy@n%Z zYm|om#xCQ}_Fl$uG1NBXM*)u;rL-+e4yhdC)7CcSx@9H&&Pe*v1s*Ui(w+|w&GC&w z5zb;cYHgF?45>P!rwn851rQ~FK?tm|5DVlyD>ZX%wPPg1eKV>-RY15X?s)rYvyK9@xmGp-rQqIZR8435#EEn5Yt!K0Ujm7d>R*i{ ze>E~T;X@ADh;a?T#gqK?z&CeqgLlRZ`=1O1>`V?0EDlEsTdX8sNYk_J?Z+N}{MZvG zP87cRI~~_Qj3oasa4Fr$QmLFxCpnQL8Cw10NV-MdwnwaHNOifj+*;l0oIDFk9k-RC zfmk`8_CXL!u(Q+P(^;sooXC(NmG;TEAt8dxxi4`4xP3|yS=rAjJjXuZ3+{i&3Q ziFh%H62aK8MRP;jhFX!aOAG7!r;&6a8|N>u&y(bz1}>!U1C`2!bR0+D$+F(w{=yLG z3Xrav73aKLH~`PNn?yYPzBKQ(C^NSIg%i-&i$S73i!L6XI7#I)Jtaqd0IUQI`GtN- z2tX*RWG7(B&A z#|ugFFG93`9ZCLmq}s?xpX}mPX^iymT%YwoKp_q& zImk3aqt#@zI*6#B>x1aLwl=SHFBJ1iGJrOv36(qUFb1FSeOkf&)#U!cE9ghUxx~k_ zOT0v0*UcYN>nTI(vwT1mr6A;vr)7yaowTqI!})hq)1M0LlihEbss+Rf|;1J#5d>bE>t{p*pRj3f^Z+|8-(_W;a8_NGemP&Hk#U%SWrKZpr5_wc+IUaTY! zSJSodN8hoa#zO;_u*W;n@$ArepOH`G?<*8rgGa4YlF4-3L7Pxa#gv|~^!UY4a%whM zj86x(C^?X@-a+`a_ckiU{sGC`?F7_2>uV2>$P>Pkp8HxfcQc|E{|oLJyr zT}q2E5_#dq_1>J!d!Yn!4856JkX^j!`{cHvKVX1c>>TeU>3YHE0?7u1vowG^7k( z{q7ZneHDy5dfffe#Wk!2P#}WgEl`jG{{NscjolqQ(sS;6VDl2CT(j&Aa~P7PIJ{0q zxTu~HaYHU8)@q8CQbNGjW0pE8THe^eUZ6ek0(aim&-n(3Yf^#FTPDHn8-fQg8(!c^ ztYFgQT$40FF{{DO6e=utS_3%WA(A_KMk0vqsrp%8hx|? zk_8fF*}kfwiBf0YNy`A$Tway4{?F=3+uZoAj{hxi$v)BEB$rXT0r0fAL8*Zlawrri zZ8=VT9JN}t8Hu3SuGi~pgl*#?vx*4-pr2*jo6hXL~a54US?U%Dz(eFZbQW1$O5smVHRy2 zTCnA<5LYZ*QWzPpA#8`>6lH?CRa03xm(q)@aQoJ3+hdUZg2pGp!c?cCxDU&V_m?j@!L{K1!Dk@Bp0y-6BYv4Bf_$|C!`IR6a{lC6}vEjdg{V+Fhflzmz;I+0_SJr$FcGA1inF!j}m92>a9O1h9=6DPG2RKA2dV_Y+n zS&gL*A9^?qb$;~n{7FClLNcA73bj;hmEG9J;->xn@f$yw)7+2%v9KeK2DNyaktCrD zw!EBeyh*yWjurMMq2Vkr!*yardxy)cGq}O90caJ0$vReo6Vl9|dLEm$qFblq7yMo) zh{oc_mNL7lN9amagUq)(>z$+3bZ+6L>n2@fT0KI4J8H!Poqd;&NUf)2ViYF6Ihp9F?uBJ zeV93tV|$BrQw28GBivPHfF{D7hba*Ibcg`9&gOVJ^;9v|a_ot8UPbS8@WK}nxL+~^yW_t58>aaEbFqRt?ycmn&3pNR!Ew z>_{EY+GU$yiI1{|mq7ZWPk_MV_urn*qpTzT-P^)dkM9lsv6c0$9ly)RNc`^Gin9`0vK^`+ISC#63)RV(!lq}yq@hTHu-FR_>$dh1TG2mz`AqIt5k+58G4oV1U zESZEn_&$p{(GJfEgoTI7uN3Q#WtvzS<(W!*&}x*L&^|%Gz(x+Ra0)BTdr^H8a7MT9 zt=;;`y|HXuhMyiZJN&HKgG7NmK7*7wL4pQ6-D-;Zi`XmXUgpV}0ck5?usovo#Ut}OFccGG{aoY=|uzlM$Os61; z+&Vftu$yptoWsTAuvKszr(6_J1&j>~t?FaZI2^)Q;+0gZ3jNgcc-?fb9I?5z6p3>P(Y4@F|Lk*fD;$Orr zZxZa}K8 zSmb*CgxgL6Wiw=np_dP&!5WfNHx3tULR${Vv=^0(h!>lz`0L#M0re2gE90V;#zv%~ zBXH2;Mn((E3uR-xHbdde+BO$+^K+XRIo^HnIj*-IYGgIU7%sAc(@yO#dyH)R96>LQ zNBUv>OpzemQ+0NBZz(SG|My68mE(Tbb~aQGQEklbsSb0dI{tj-hzqSmF!`*nZ*H6v z-E4!3H-Oi6bGv70698P_wYJC7>gq~SW|*esQ;XmtvvZfd0a3Hw!(f{n|M}%Gb1nA= zh{E?+mq1l(X{xDZ)nIzHA55)gjWmm5v(}JfOilb;5^%*~RWiL; zxxi4PV{BF{9^Im~@-8fW_)=^JVQti}Oi!1}47Mtrpq@%@ErKIV8$K#*Fx*SM4-&Du z@h%Tzr1B6mcPBJZg8*U? zhVX7y!MF9~2qlB=BjSZl%WTAYM%m-aK$AF4T}g4nsELK!6A|@LIz|?Hl1>-1 z?{T<>%}jLnTDYfZold;^w22Dc;4-N!BCkY5ACOsQs1I(`KV$t-mMJ2!M5BnniG;zd z)Rn2z0*wcx22IIRUnNq6YwS%W@MYksY3L@+uQVQr<=G+t)p05}3!&E>JvKc(&GkT- zy1BZHwF$ZAidD0-4H+W0>jZnm87f`0VrcAOI+uM5n(5VCV$7BhJVznYPL!!C3gG3+ z>|@p$&aIw&$Tl2Y3=up6iPddEab!~Nw~Dc6B~-$PUbn!RoFQU-+~weK3CG*FnsoB+ zLWo0F*s(zQ4ov;{3IoNU<2820wh70>jZ^p-(xowB{v<74#JGgJ+CgkBr}vwpmFXOa z?yF{X=Wuk#LRDVrCUoF~79b7isUTEX5jm~aMzip#=P;j#F;Rt+=HPcuUxg=a8aX+( zrZ_3N$aKe>a<#B`({&ZAN_ubm|a1G|x7 z(CYTpw2%U;x)DLjpuUzsrG@VP77#094fioWa23+o34S0R7aVx$D?(~*GJY57sxDF+ zc|}~?@KR-h6?~t+-aLV({#IFe-WC40d|O{rP;gVXb5NX zarwhuTHV}Hkyky=IoP!TjQK6r&J1OIyN2WPm z4h$kWc>I;i8OvF4TF{p!o=?j}(e@N(;S}U4kB>VB^yS-u^F?m$bfo;DU6~-KMk>~Y zjyZRBM07AT;%KUUA>wi&I<1cc^*UVp+EoLXpm9Zsr+`|;JV2EKaWK4Qh|%v5h+vlk z*Jv1Mbk@>MyM&)z2@k#>WQOxW2`(~@Zt17?6k#*iz>1jhE~A{TW_mcep#)M z=aNYjM#%C*iO3D2+4Y=rQKZpdiYRdZb(<9SZ^Fp_n7=B{h$RJ$-#0Wr2ZiuS?4{_{ z_f*iIA1qKGvee=pF?wvuFSoiawGQ+Y|cC8Pea#vml0nJzcaKq5~^;_PDtb?4s)}PgU3& z=ywgmPCy-qnpWdl9i)|W?`day%8JY`F>J|}L|$5V=u0|wK0VEsFfAtYQGtgAVj{+- z3X)wS2o7rk%d}YCWxsGR6uUQAeqQIwPX=S&(k7X8BrNwXeCY7Q=^|{E1%xpr%3%6D zUVJ|E(-2?QN@IACf9j}{HNMRR&LBH-S@NpC@F#Pp>x3;X5jrVXrNxp2wqUPyN8l4w zu}6&vi<2BFbD?I~@hqdxCbDPSxU_pODYWYq6~mH!aolQS^dghm0afB1CeG$0wbYaE zUvz~g%29Hj##e^D6iA>oZn>va;QU}J5E*@MJC3m_MTP}Aph`|asK!QTrS3GO^sJp% zD1_iXY=-sT_2kf^*G%K!#Sz2oS!jxYQM6CT=EWxZDS)dWD@F;0`wmIPS%wQy(u)km zfx$nFzQDm`TJd&4DXGnx8^OtN-aE^gilovY90h^sxwO%{$E?79RD6qQ8`;zK&7EMt zk_^R#ko4!1BT9|Y1uRh6OSC@GtyT!-Oc;^Da(dPu369uFYnWiwA^HM3J)%&FJ-!d* zx~GKhsrmH0qM$--AK^+auHm-{YWQTXaQU@zW*@$O#K;o_buCo0*Evs$J=-uj&Mcus zJxu7+PLxM;qlI**u4EX}3(1tRXP0R;T@osI`_Sy8w)PtjZADZLoPlMRrgL5pBQc}}d~9y%p0PaI36Uxqc! z&mj|OO`^LZURfPmgq1Cttn#s~$Im;KFP{XPwnVNCCm%7A=atifdFH(2qte-DZ#M^c zM5-{v(1|l5x~q1@%g9+%JoX`K8Ug}uA`e0dCFKMxIxSmJYr9dO*ZXo@%3fw4l?WYt zmHe9ce#&zYxvR~Ex0~?ma2idU*Q`f$WVKEy!vwY#E2+nAjThl)=53*oI}ye-wqacv z3TMl*_@B}xdsA0s(DKNEY)K)IAb`S;`=;_v!OkdOM(&nfgP7XSA$?a61dW0!6s7}` zHVJaMqGqJa=s*qZb%pH9kROmB85+BN+CC9PS2vF4U>XxK$Id~9K*}rL$jtGfgR2Ilfx;)rM*Us zAp!PdZ8ZprT>0D2Rnv1JSH>uqS!tDJh;&+FYT3?21ePfz(}djzsjYwk=vOYB=wRJR zUBYZXYwH#-cC;1x0=W(ip5clssPk^kh!s<(h{M9+s|A?Mg9|>VaFS#(6|cmK%8@ol zExkU8l#PUkK-3!Ra|Va(3iq5s9y+*M&9aIzNLk1y!hZEqdODk31O%C0m_PwZ)t%1G z0jdC`g48av$9{_4Tncc##+JI;ULT@{?uHr!RDb! zfi#T5V{PkpKANN2-%#s;QU&}Rn)~+qVt)jcT#wCZMK+0ha9iiH&dT8{oAS5sARJ2j zjZApgn)`ha1Gbd)YE@=lH3Z*DQ(<6kvaO>GZsSVV4N8TqplwfhCzzP zd(oMWQe@g6=%;Q%S=B?a3W%nM+uRIa9( z?bKFK46uqf6DzmX3@sBf?Lv~7B zsV+$;$CLfYI!d5Xx$?~z>soM`q~rRckli^v_EYj{^uo=pV3*ej;TE4+GX-Px*~Q3^ znR`}Gj;3Ao-Mzc04}t3l<(!zWGDFb`({c80mMZRyci!uV> zwUM<8o_CT31dXo8jI$aH-(1gI!UH;2#lD@ zgsT?j*s;TBRxG){l6);a+4A2{oLJboMuM@jUaxrWe@e?DuJ$4Oaw1M!NoWyLd;iYy_txJ1?iUjNN|FN)@w#fVTH4fl z@21v$Q|qNo9nUu9wHb>00`t#i8`Cuk<*=CPi@{l`3#H&g3wyA4W3ii zY5qhaJ{$skcdO}QY2dTV>nA54A$6Y=;tsXs)fjw(2-OqE+>kLj7-*gbbZJkISg2^oKLa?obyA5fs{D`dr`KJZCVUQg6_a(!o_xni%a+6P82SB z7qvNFrZ#UVZ8_`=NnPTC5c8#K`t6eNAR*@DqBtU9c&9D9=(kUtxU52h>#freYLzCM zROQLQg|no>15-aZ;|YPH;#|_K@~9C61yI4rrb3jc7||*b25Koi8yfeBQwI|~fgv*coGpGh!&Cu%k!6I$+-qJZd0hZ(NK`VxxJ~EH&E8#lqZ0of74t^HFFqeb%Mh!VpzM;E(J!Jb@J~qh z!me%+da5vdJe>sRg0!7fCg#&Kw>uOn@E4oOn9||ax1+!%WF9wD5wYfEX~7vdE)(s7 z2L|2LP{5u%8J4CE824D|C)LTjQvWgE#N`L8TJg084Pfj!TYTe<@9Ou9P!x6*9$jNy zh$7|J`_A&Wa0Y}?ZqvMA1&&9Y-b(9w6Gy9bl?c>II){;rrh0|X=yh>6I5=_${&sTk zke9v3e%-v@vWpnsKPhaCf7n)ZO>=RCTAsu~20(hqCl`kekllgM`HlE!lWUqWY&!}p zmo#{AQQOjI>E%8c$WI7bJ^RVPZM(OH+k(X6!kb-uy?|O~0xu>x$>H_e4mbS0ejX8O zbVO4lFw%GN>T3C=|*4HD<3= z)X`utSWaquPsEtW&Umd$i^Qvh6Uz2yGIsIRw!k1Cwd)^taO~hxdffmS;X*3&MV24K znBu)Ep+Q(AL`=?pY-IhQCit-MQxRqXR(nC|Ng>OApd?37u>3=}vb}qxdI4HruKCg; zymC0_YX`sCas7iqZ6tKog8sUNaw0)=6Yhjn>6@q5moole7)qz!XUejf!ISFQ|2!i5 zRH#JJqMR~iEP|%T&FD(a7f*18ZA{+e1poWF6ha>xk;sA7@Xx)P0$u2#rf$F>i_X&@ zT%Nd2b0;VP8Ybj62!PVfv4L0^vvT8B+b_OlU5GY_U$J%JKbVw%Mg+5js&1UXtxP4J zqh^rvuf1~$A-EPpOUewlLpUp9DSOD)&Nc)Zx5D$04tCi!|DDml(Yb$@R?oLcnHzqh z4Kbprlh(HkrT;k%rEdV4yO%z~-D-6VWo`PI=tn|Ft4tLPDYyWu&rDa64^=6cH+|v+ z-=8deEjLp2nPZjY)B65cpYN%#{LEvO`nK?6hD4Dsm)7su;xg;dFGm!~6-I~Bv7PtKBq_R{fB7lCjRUFbI}?rG z`&f%<2Ok(pXG#_js!=b*Ns0g)+w%kl%vC&@H@yO@=N%D$P_H?cNT{B$HaS18kig9u{-g_#TG zsN%?Ka<(w?G<9@P)~Q?N5&w7>Pl+a*;m? zoHl7IaBcH;i|XdGeRW^!-%j3^Bp<<*KJ>9_^0Df8xSxlt&%hzOR$XMGHhzJyCvMkt zxKXbi@zUg6nBxt_JFeH6+pgM&wT>%bwm)H!ZE3)l2a@MwEvB&ENU`?5Dlr?h-(lr+ ztoyc9l2h?*k=*S4n%8tAr;YHOrO&k?W-r|tH0vJJd6>H7JG*CM<7A|Axf0j87N$(k067#}V{i=dAH*4H#^nml{i zu{R=2Yo&ZSU(>XA#hw0OU)-r@9Yu>2X8OBiP(9Nb()P}iF(=NZ`?|fwobEYu`j<3a zoL8a{J=4k(N5-U(CilstPF;!C;~>IMeELs%M`#5ib_;reE5x z&iR!)=$zlWgUmd<%|v-dDFcFslKCp%BRDx&lCYV!5Sb*J~{x^L;6Z|R)BDxLF>_u`&^ zEJXfFHTkA9n&T^Gl%fr>1+UDMY*gw+np99j{L4a`Q1Y$Fr|yaJ`Dc6bsMo4|?ltS{ zP(JsX_O95}Tgs;_qi=C3mBrkL@`+fvPj2hYaw@}0;A-%>t%Ew%l1xVMzg zl7ncFO8cui+YR+GQX zW|}ID?($-6mCd=d{p_;iCP{xT**kI|=j$EX&CSM2Hmt*Di?2-gN3&DVw>+d4GJI3?tclHksoN$oUvwNZAdw2!=PrhFe++VhDjt?MBgQnYy&7^zb(tjy? z3(0$yx3Jp)x@REqe@$jCTP2VyJKI}pJMH99a`H;c(f%xvuXO6Hz+lo--A=>g9mx~} zXVuwZW%c%eLH{k8v@2-WDEaUDtGT*<2hZ9BX(BJSI=z&3A{i$NVyluw%Jx8V2k|oJ z3wy!wfAsZl{Ldu$t7>&Fq?<33$wJr+{_R805PaFL9?7y>)1=s}kB0JxLSd2dbY{W& zX*KB*O%^m=9#!s_`1-XKI{ck)tzT0U% z|GGNXyWEf@f5?uYiWXg&$E|YY!e+JBc%iaBi1jCDf;t;4ZEkKy4bVw_iQ7oh z$_KL#RsE5w;Z6FG*i&99@MIi&o-!+V#*?ASx%iP++px+VlkeLmxxm44pmK`AR;<<2 z#ag##vbPA@Nu}~^_Cd|sP?CA7y5nbOy@t8_`_(FVB{^96uy4&vO2x`Y9ynq{9;z*F zZk}mwuk9wobfsP-QZ{szK?$_9C`)qhY?8;NQEDwptfD6ZD*l^LbA{Kk5_VWW#cbzE zh=|*4CnJ?#FsafQh&B&3F$LDzjJkc^pom%tblRYwo2-M_?P6M<;kbWFelTAxCU@Lovd5|pOcRa zr4x#DpvZCZcW+&aw=RX#ssAvS;vb~1{bM!x$7*#Y@Fz-ucuB6Mt()7;8x--Fyh5FM zg(tPPOU&xwkFazjIf*)kN+%umi*IRZ#E9O$4d{1 zg7;%;`+N7lRL2JIy2S2eG*0KHuUcX0#Z6-+N#)zrsjqO5*hO;>eGQC=!I<<;75yZ%oCXNch(nR z2VxEO1R-};#3VUTQP+fQt@)FYktj;>7Ujhz&u!ic#|p3f^WsC6OKWFZ$ah|c8Hy2I zb5y9Gj38QQ?mk6hA9(0(ErnhkFfc8E!WMzu!6i0U>O^j}J5RN25KNTN4d6olb<=>e zT|bxc>lIexpoJ}dIR7|@ms0%JB`uJjJr_WiJ=BKleU=Ylo=VA#a}}$sc}Inn`n0os z!d)m41B3gx|E-6#g>6!1?5jA-@^1Y&f!fq9xw;h|dRyh={>fes-NZPub6wSa$feB| z`m}1lC2z01l6~R{+WrLOwf`3;Jttga9uN%O4s94&8> z`zn7U&JTO{riE5mMRG(arpRvv(7-||U7$8i%bhy0kT#u;%7x8et9<`Xlf5L&l{}cw zGZ*H$tbJ@OOSJoS6LSD;{3O%@i1m+==9;xz67Oz+$J3Z{9aw2B%gGN+M^pc+J_hDI7A^R$)~Yh@_nPl@^ceubzB+ogvgCwp-e+1 znXuw>_m5U*uB!p^Qm8LL$tZyfb0>2>fyL0uz#XSP+B-(ax?x#jh*AGLv{fWw4sFs~ zA`3|>K{sh_Q%^N830<#F&?u$@EeL9D<>d>Mn2M?gd{Gj5hX(X2BFTTK0MfdhsY%!j zAY-VFpx=ix^rOzQf68c6t_V@wY1N-CEmR;hK9L-#o-w_R(!SEiyv+kEG*TBj&}Kz# zsdd3ufmtep*q`DvvCq7!?YTltagz0!x>L`TS7@}a92K)t`Je+0s^i5Q#r#x&%yt1s zuG}_-J5#1I)DF7M1ET{QE>0}VND})(`29CYW}*?mex@Vka%Vj+q?c4MG5Fr&msX#} z3iZ`bS?SB|v(5H}O|!rt$=uGyQIiv+gczz6c{E|(&LhCJReWTq^0jCupm~ncKtOA{ z3sX3Xv=n%Ed4lPM&CXR#aUB?QTumLr7&$?Ns$dO9;h% zR_f=Tu@?p8KO>Q^jR5qA;*rhJdm$PT1S3QoUs;r%-fVXB;ON-#KrweZt81&B-O?pD zT&YAE^fYMShEP18-86Z+b*sIl*qgohhjJ1{Q8ua(b#@Oalc_y@Idm8mE{#R?Bb}H* zH2%LkD>v*++sQfpq%15==GCd~=~XKJv%Rr&^7pba509q5$n63rC*#E1My{r6*=#4n zng2FD*-CFwo?Nfila8z3+v>seqw`_q^Zb3X&w{W!JbYKoiYqGlkXhS)p|5>}U`3nq{$svd#`VZw$|& zqpYA<%?~{1=R7`^9u9XfDKYvnOx*?Eolejx08IGTgX!^jI1x9_2zpPBCiA^3>Ag2| zXSpXb=AjS-a#y}Rs1WU=VM!FMhATWR6BHD6A&iIDsACT8XV8;0-~zmxhVLuT`%)cdE|> zHnrH?zR~I=Z>v13VIBLcG%g%NIK;PC)-w79zY4dX0+BlOOliz8s-28g{+@ejN^V}y zYdd0`>i(Y^39uueue0D1A*upnjfVRpxjdh@E`O5=(nX4Wgi zC^$6YWJw3v>_8+x%NhzoY!F~!3o4$LSz_uZ10l(dxmx*K*S(8W!plVKa&8wl%`!P_L^&_LL{WlgoDs-KFaGaBkdJmUf7N1cO6r^%axh49xZ zjW}4FqAq>>sy+!l_bs;}pt<7c2qq3=YGc4{u}A03m20i&EA4c56m!G$iqhXh zo4_u&abHRI@O6KDcQlPNLG?24^}N#mkG&^JcJE4#?3F`5)MtmY=4Oxk+}93~5`z)= zwQer?NG=^lSeNLdX;r6$RkLn%hc^DKgsc9sNX)u7@)N$P6!on#2@H3 z#VHO{zT9_;ygurB^EM>O5K7j?D?~;?GAotu_I<8xUt8fcbqUhOS7IbV`#8^svsKAQ zi1X*I_f&vNWE|8nQQbGmG4VK}ufO>8aOID>pN>SP&kRwnga6tFMk=q)X4A`nwH)mr z*(5&F)&U2}4o`RxXQ@`cKVX&(F{JJpIDqAy*0$j2(8E*lCZ4QTYeV3Kj<1+23kn?oydwRbHh{u7<~sHQ=W4iF`j1xDhnQU^v^iSN_!2 zv?FF-r1kbZ@}^G4AQERdlA*7JZ&A?<*^0+#Mxz2GgViY5&#;JZPdc1#_+woU(+jW?Y&eC)0W?>mOwyzq&F0{;mKneP}*K9!4hU==Oc-)0DPkX~p%ZFL2y6yI5{=_uUq&?h)p%vzJFzzS1e1xa0eSh5IwYug{Gp zpDP)PzR+tZ`T_$%t}l#MPX()HT*dZOT=~i~?;anQDKmX&xGeI_EJ&*Bv^}Udoj+JI zA00|z#wxeGy7EMto(xr%6&tj3Tm{e}`jl9}23bwd#<$XdCwxfwop|^|!>PTS9xOf? zULC0K>hgob=__H%!o&9E$)~6v^x}mb#nI_S6YipzKS)eqaluLQ2cu&>`|iC~4q}w5^BW5^XS?&4!GqZL^Dv;5PVim8He5wT!WR^MDJbCXtve5{fh z^=~>!{gNr!Um2Z9zH(Q@^{c@;`t<^F{px6SIv_J*#qC{1Mc&?>qb1OW>CaIHnd(bh96#G^-%KZaHaCtPZLPeqBSOk0$o$R8)E?vV z+_d<|?k)Y9w)Cyhyn1p6I@>lJU1=XBeIjHH80v_TC51&a1rdd|&Cx9!cY79OPz5o^#IgJm>j; zjOav!_NKi6O?_g$jnFnSGVyra!I+7raj=NCxv*$&nZ&%EDNGpYhJx2dimLZdbxlO- zSU%rBr|>_Bh1>0tl~gef9z-Tg8zW^r4yOV_m&cXRVLWcB5-!Y@(Zw9t?tR@+yCWAj z7IQUAC$O3k0Q03!r$Xj>2j)Pikt8PW)r99>g%@1rk{l)$*H$JNPPw$uR?gN<3kbvW z+&H15uZ+w^0K^_Tzb3oLRD1r?Il+l(M)5{kd7acf+I0nn&*9m2b$#4O4Ilj{la{-{ zeE^+a=W^X@-SEej`fVb14oB`CQ>5A}^8BG`c;oUCf@t3nv$?}+Bsj5_)`dxol-~A@ zA?Mkr1oq(lw-A2tnWPj)z&8@tA)>b}oGd8-S?$sCq0~t46G;WNw6u(sF7>?je#59L z?geMMjN+zhuv}a{x71xLCeJMSmxK|V@nE@awRc+wI#py(w|htxGd3!l{=VRv=Y4Y* zJkY)-r?`59?m4PZ!Kz&0l7yl3LOkoFVi%>0DE$ugpjcEpFt)N)57U*gP0q7&f#|}w znao@LjQE5~+*c6%OdNf&4s+ZEe*HWYXsrl-Pl3@=7NNvNUx zIUREEbDGwRbxnv5pe4PCzm- z{rOqHoFr)p7Bb@E5>!vjA7_LD0RRqM3gja+5wDW~>sPRIMFX6mqz<&|J9C_35+#gW zY|5sAjMdk4oo}_fG=U2K!MWR9Tt$?i1>>i#cLDZ9#k;b}7y@jI5j-SDciDyRPhD7S zU+0QyCYi?AUHitw*1!{|Q7%Ov>}98NOMBV?N1s%bL3bEo$}z84NFR_#yG!bY;y2)u z?n@G%4H`>nxSxVjunXK)dt#b}BT|+NcePL(59Hjcua3WTGOeAJz*+}SDug8lRGd`@k#CPNy zT;e<6i+#rfoC0F>%OtppJ>Hm^*95E zWoy7KTa`PKKVhBZJ}k{ySryM5fVniP2S@yM!6FX4-vX2M!Pbwb{ai^efmerGFk_{`iTS%Q9Ip)A4Y-;jUF5Vla z)2GC2&xYNA$pvkj{hp7mE-Tq!rnTB$r`6Q5gXdhoaPOsD8$M>=2semp9pRExY!B3a zUf1=GEc^POlJ$Huv7QodT+i)~Qy%+&BEXagvSx2-5x#e{qr5}2Jt92N7INlbHLp>k zf)2Nf9WwXQ$?J%PAo!Tb+9ro=W!NXCxmq64p z(R4F?5GhTeY!TZMToA=MECEg%$L2@_JZjTGSlf|?vNVst?l%nJ&ItDb;5DCTaCHF# zD-N>r3f-a@#46OftCBZT>N}?PM?u9xjGukC?zeXiOb6qT&0r?ZV63tvH^+zfGMhE8t8jfeJ3w8V6?n`OFr zWV%~0WlD(r$KtHpk?VF1Zoa7-MYF+a4Dsxl?-T-$(*tsv#T@&FMgoVjMGBCUgd`ka z;z{kIRgFpUjzSO=Qrnj+EGz^mi$kcNwmFX_fXO~0lfR+henY{v;#?r1v{TZKx1l?_ zDO+YL`=F#c1Q=zj5lXL%xy?1RfxIp4sPU{T1Uq&Oj0HMd=`8p5>LP>b&hpUBNI1b@ zneEMo{RAnEkt&hZe-smYDpjt(akKbgBKr{j?N+KO_2aYyamoA0=@@{H%< zvn~1Gwrk+=Kol`&cqShjDIn*W+U(0p53!+L)zJ-*fEFS%ti8s+A|ByctL;dFC( zu=etNA+J4{hcvW(yYfTvw<9kft3SwC`*zjpv2CDyAA9tE2Dks)cJHo?f7zb}>XzKU zYw)H}W7v1zvx-_oGKZwBEku@j+{*kFx5!$ufq!nsDi3s<*JgSF2TtFTPoHmIi~Ssl zf3VsRH{K$6VMUG1UUah)sj4m(;B{aQ@prP*!<8~SXB9%y^jN0KWQ_+(kFW|M#eC&E zvlhOxqr_rQxP^0CTP0ifn*A)#1Q#t4RMIM1)gIc-E0uw_)gDhz<-pf?iCegZ>-(>D zxZLlEQ>Tf)52)(z_S#9~RVL@!cib`!nR+Ccocm5=_LErO#WwcZkPj!*d5A2qG>_ zX6S|S$H0SA-ho6F0%t>Qlv`>~Y4n~krAkIQ7h#jF%?(AS^eyl~xFz(++1YDy7}(An z?=}_Hd-m8X{Zp0!6H{Cs#X4QHH0G|T@L{UesI*OF3a8sEO<{n4XMn#FKbqw9nfb zT4Exht{Bsm+33L;CH=U}PGOdY{8@(&cev21yF%7yB(Mh7FEle*>dwX=d@_&;ztzY|dWsU;-k)PU*u-p*3tI5}-$W1e9>f zVP8q_?R^=46W_d59+K{tv1p$PpV`RDvXf%($9H9?Hyvc$C1VZMn_gOJ&exKG z4bC`xsk`(O?ZxT{^75+=9S7mNIQMYDa=n*g`*51?v5iOL^Ijc$@?k%I*1w&-VpE6^ zGNn`qqDsnLRY2~ykpbn6K)RVV%7<}wbh@UhCevM(UDy=BO8RSkd{TxmmIBJ3S^z)c z$fhy1LVM&_1fmP)Q~WA?t2k8oTK1v5_6^~A`7Ix;@EpGF3!hbcDzsqSv_}Gg;X?__ zw%X^_pJU0Ekf^IzeIw9gF#_Jvjhr;w2PP^U+hZ&ZCgjK}q~hZx8xKPU$X8OR8Da?B z;Z4M0TDGkhP!J!7U`w9PXT{ziwZp?75-vDTsS51!`zd*;3!|RnZ(A#+EOp~lFY}#6 z-5=NXA|&2YTeiM*u*f(Mr|_K@gYWkw%J~{hbee_c9Rz>*rPvwuVT#7Hu0+0SR~epl zv3a@2ozcCOA^X2GORaWLt|<97KyE~SV7emhq0e!;UwwU^p9|Vv^;fBT*c4Z`{&|G) zW)O6V70&B?8l+kmcdJd=BU`~Zc(i#rf=GElB{6J0-Vn~<`7M9WnqqAuS$mVQHq|$4 zL#J4(TGrW>kLWKuKA_kSPSgpDWQT)=P;1HG$Q_rc08&?Sx5W<`r9bQ2-PvS%w%wIE z>7ADC}4|AumYRs%rg2@ zZ}c4h?>R1}Xbk0h)lTpthl74oh1+2}wtK_*fne}4#Uslppwe663Mb@2X%5}?wPv?9 zWA<0dCT!2gL80^3XhNxgJ+!*08W?Z{u8V8-Ub`#*;yN|s2!fFvEM<>YVs!QH=`L}$ylz+iu--kdha>&8oxN^rz-$2fs8&5_Ia;?0 z*BR^=&;rgP5q32zw%~N$KauFC3K77P+~fyX^!x=aQBkjap}>YEMN?h0&ZT=H z_#UyPMED|LM`6{6WExm2Q9au+@DdX_oz-cc5-!*9c=}${N8MboO6h?RSxK5J^db|=!y8tG0L==;5pAZqZuq90_DmL_Vt^!L@Vzo+aQG2}>UR*R; zHHNRoIjZkc{>CtB0_n2u6l^gC4}Uxg>VgDV1G0fVZha1td5K*M?HG6#3-Ixc_NmS2 z%<477bt+1-%n`9Y&OR~Cw8~IUgLskhsok(g>VTp&axy2KXGh*R2MV`W4j(SRoI%{c zQMlM@6m@ke;{d&s;8~fvD3nm0H%Imiwmq-{xU?LOc20wa5_pj|hec?34UbS~@yD5$ zmZwo1ILo^#33R^GyMo)-qOtp`2tmd;b-{7j6xwzbE;fs>(Mo;Q*Yz%+_k*MWBnrH* zbeGoHt!&r8BRN4oVk0pnlC^zx;~Xt^x*tBA-flc1!m{|uveR@YVNar z3Y#0qC#0mB#i@RU?}cU;{Y_h>pk%em6;#>F74xN#!7Y%huMu-d$mo(IEF`Q|?U7w) zAs2y5(j&o!?41Rz5&)ogibLV;fIwu{y&K(^H!(NH6=g3XrU^{%&nOy@Jh{XLv>|s% zSF~T|T;c^qhfvb4xUI6=`Xf zuna|4nw>2~`Z~GI5z;{3A#}_-rx;*#e9yjRb67(zVMn!6kZ-qFyc@UkN~byRyq^`B zaD!l46i3}`Pwm6u)qwb7fA|1(CYl9tJ+eRRYTC~|qoA>VW%E5UTO2p{_tY|)=E+50 z2ca&)P`@yeXPe3mR&U;LoxY+*w=B-qZh?`#xtC*^L+6MgG!z)GW!d|74bCqw&0lG@ zmQc_XtU@h6y!@P5>`d<<7V1l#%fW^y*)n(HN&3xq=P5g3t!_U=zAFlk09lb|fZC~e)Z}L0VGF2Oxf%|x6 zhd9)m=DG{ZIJB5hjtH-O$m%9)pU{lZ-v(viBw!E>)yPKs!_3;1&ffNT`N-| zSh?sYDnH1+NNh`-eB&}U(&d>hTWdX2xBx=Ig}olrT1K=p&8ry8nisA5-WFiY1rToH z)pH?t5v9To(sEzWWyh?W`Min=yRvnW5U@}w+(D1IpjaGoyA^yZgV%Ef(q z{JGNS#kCpZgW&uYogPGK=U~GPT_CbN#>0TGYd=PpEIy2EM?~{(Z42#&xPc**6L;q;N0@nOQ3LjmP%Dv z)U4`wE5_*Lhx99P1Nv=cWuBre{j2{N!nrat;tp!zBiq}Im1Vym_5GK3W&A7k{e8Q# zU)?oy^9RjFg}g?gBSW7v?Q0KO$ZF<34TXvE1L}p80E+Td4=ntHHd?XZ>) zX)(TVYxZmU?_IU*H*S39j#%|iA@|%wg_4hUR;;Ald9$L{YG;Uw7|8JndGn`k&ChN2 zI);C9(#mUWVb`@YS@zMJgL|=sKHSu86B$s1uT@uME&ym;zLkm z)u)JkDLXkq1L?sC^0VE}GJ*DQC26qy6_p`0A2|WbKqjv6F-5iXK67i{U>!V19?u}A~9IrMRVN|%JskX20t4ypq+;j?#ryvM5XKXhKD%po(ok|PZR*MNR zO$#Im(PUH?P)f3CFP;k*bA^~bgDZEaT})c|MWnka#94fhPgW_Qq&|!MPI35zdi+GY z7>}Q#%c!7|D)&AX+q7Lh+)Gv>L&pPuU>_@%;|nE5ob6uVJ|Q;Q{FbM4%WTAARSu4h zWp@WCE<`mN@$Y!8X`zu85dP>mRaWP4q~T>-<)T$$7+BZ7ezsEH<5q54h@bo05gkmj z>J^Q^m?PW@fQd2su`Yke+az%R90(a|F(k~u)dav1jU^VAIpwA6xil%!XK~Ol;TQ%&Eh%=A z3b?UWb4qRZa2%Nv^)@N55lUNE<5<{4R?22j0YNY>t8Gm&j@Pa=iLNB)yJgDVR(r47 zJOU{OHp>(dPds)XDL}_`ajw+zj-iQNa0LYuNK22@t}=y1@m-!x0oonmT-A@|aNk#J z04^dK{qqgSr#bHh%6z%BXqu|WA@Vu~_rYaMJ-f6$wr?0rL}?J97!lgv6HdzcA6x+4}W(~s*OamR)(V#kCb%3u!eu+S)JDq0Xzfi9R*9=h#iKR___ z#0pkie7WL};E{CFvR}mtCU9kdHcMF&&@mlDeBDm7Xq-T!dL>3I(tEpCSO+vv2YR@6 zdoHLGWwMgW@4m!ZW91RTcU6Q*hVm2VPoIOWhi=FkxGFs$qKPdhIBa}M17^>yeOi6n zXJIwqodVmk_^YFa<*`1L&|rT&=!}pJ5~-Ll^Mfvh+Rvt)Il0)@g>bYqmA+2L#jQlq zB^vzDz`Jbj4d|t5){=(Km9i9l>L>|Nw%}SHM-MGeb?oNs3hIC}QR!{am6q@T#~a zFcvwSJ_zN6^I_f7j+uOp>V~E>-tk4X7(6t;XWdTs$z6k|O4Ap0KUfeX$#1-FlFc{jNKs4-xKOIY@nHMZuKdp2vL{OZi7ekn4o2K1 zDq49J0mDrCYhR~P7zYoVKz#4H`oHg``*z7W9Q4-SZ3B73M1sxay6jWZIX<&1<6ji9 zH|L+*mGLj?$gYfkY5KygI|s5a-B^e{B!uiC7b_QGF@_~p!rz7!Bc}8=n3C(|C&Rmv zUq4Z{f_~lw^etm#JzX1xMsFtVx>a%#o40j@NElIM(k3Wm;DQlI6~mM01CYF07n%ph zk?6wMngTw}mnHh>VQWWFQhZ;el`*yZ_eBy*IMN(d11!j|PlZKRd8#O{7*VJ!0q2{) z&om8qT_NQ2m1oI~a>%IeLX;si#Fe={JT1;OEVYnXkAf03Dk4t4rC`+a^?CdH-R^B7 z>8qR?r4mE+4GBDuV8ELs=W{a=z({td356(FWwNgc zhOh6+zJ9YXjIZzA#<%#9Z<4B!Mm@f`(p}5)u}ud3=9V05!}pq;b^h(X&TaZ=%Ka9` z?Atffv45KlWN)K-gf94b2dZ!sc=Y^Ttx3K+%XjzHn%KRu*2HeBHL+v&Rt)%n&YrV4 zm@pX=RyHu2mWQjc`)1_Jm1QG(#q6I1x@9{VVcXmC>^A-P_TAa-yKm^`7%5TZkVc-c zBxIeAR^3QDvCg!ycUebtr^QkXYhVSxSrPaY%;+Ddt=i~ zc(rTA-*5ctAU>Q8hwEr4w6n2rKt626AbWT2&L0&KN9d2KL}XtXDrGnc9q!tl-E}hy zaj>teb<+nY&*ED^{)Mg9L=q7XvTScqxCRt>Tlpp5-1O9MuI6?Y0r_)zw(sVEWY11@ zF12UZW~o@5@2!A^=dgkpkVOS&wq>UrO2ALZLr&Z|sT0xwfSLZXt<_dDI5phMDr-o0*Bp!>a8l*;;<-XyQB=^i{LT_wL`FH?+^^(~Wy7Ow>Da zMhR|X7R$GcBeUJY$1oCn5NFu?`?ahu+MT`Vrhr-BeFSDKDqzu;njb*M?p#F)N#@m; zXeg#Xu_c2$6j0=4OB0e;U~zjUE@BE~%l4LSS$2HO4o-v)&MPLuuOq~Ww->!%`V8G< zCvRq+c5b|k+p91|O>{3KtqtY}F8i}-An(ibx}-1ZtXY1<#2WWP$t;i$a4UG9QUtdX z7cnG=?uG31?igQc41x&qpQ?IL`Yg%ErT&+Z$`Hb^vSZ^ zk=u8OY*Kj%zgTk8l^EFeTC${vqn*W|Hs$qVx|CX2>6bJn>o*Vr;^4d2_ZxoVQxqkO z&9k_+08!X8M8NeQ1{60Lm9mC$1&bp-WTZ(mK&~58`n~9tF_9zvWB2YA4@C}^Y`u7g zRqf*gk_x3zn6_-LlXDL~Ad3*imfT6C_27;9c2S-PWj7D3S`m(0YJYHZ{kUu9l6{53 zZ3yAx#g$3Ab z%25tJ5xbjQTBVqqjzP?nwr*Z3Isp{12DJpW0ON>XG+SBz)r-3HB#P9#Jj}|w5e#LJ zn^_+nbx6gbs5klyB6~2>#D2$1&^kTkA5sy0;8ms=ZJ1En;)MLA!P3iAZMqN(Z76sV z`_uu}8R_4KdDb=bSBQ^Z*qvRtF)JIxIjPtoZ0_CZquhQgZV?=q$C!r7jByEDxnaFu z+m(;&3`vOB;_MhUWuUeBj|T(R%E{WkzFt1bUL{B$*_}PId!SL;6ik9oh6R!;%@ieK zmfoysOUo>7lAXXu*5M;34(kC8^EPbm&?=Q;g=81Y$nhi zVvjLXY!(eOE?<6_e_tq&#RsjAjIqFq4Z$$kf)MiKdG^|yn9rEz6Xud-b&o^3xpDF> zfgJ%^`y;tVH$X`N^w-Xsn6bJ$KM-WK6IeW<#v3fq$C_x3|(deE4d6_V8gb9g4eW+3Pj@8+T{?3+WD_ zuDq2vv_n} zSK+wAM(L4T2vTnnODI=A3St)DjlV-;OJOBWDgepxc`QV8Au&;VS3MZP08_*=btNZg z@s@Q!#7{$9t23GHwO`+&o2p;z^86A>Eg|Io`nrAvR@l?v*nOhE%iv0jjL1B?g7QG| z&UIZ?y6J;*M=0~M#16$N$Ny?O5|{JN&5pR`1dQQS)^);5+#L5SVQ;C;3H-RRQ6e#U zqPKoy!>565AwJw{gEqj+sKA+07pV@Ms53pDY3O2igTi}NBL*-gfXKtyBG5Jv(*e+R( z$yI$-l9ol$96lT(15Z@Gu?s7ED>wC>60f`Xd=wps&pf#IP|eoa7TJ&3RI57O;Iijjx(;339p=4PLhhZWROf;5pr-#{I3colBkB zWOOP{OmT9|x2JTUaadlGK|hjQqkf=rOmM8?h-NmZxaJ_y#1t;0yS$!tX8Lox$$DO& z-=pY{rNtwX*LxIW2v?ogWmxq4&XH$Gn^@tJ-cZLOA6DoCi-srv{ee3EL%V9nK= zA?FT+R_E-eU49)Z$MW%I%Rl-2)>rbm2lJ|>w^2(Y}BO9!J z`g~Ye$(vdkA5w|j^|sz~G>Kd5sf)#^uhZ1E=5lu1Kr;${B&z|#0vM2b64$CfALTZ9 z{^~tG;>ti7@4;I0KEQHh;nyl(B>Tj^ws7keER#sL3aWE4Umt7vuPt@2JnJm#7bFpU zVQ2QsTeFEh#-ukghk;4q1;mhivC(jumMXD|;PzopiqTWLMbZ;n-L3)8RvZlCwdc3m zUWxwgzR72OResgvBOJ@whxlvz2p+v(+s(h#(2C#K7+UcgyiELq{zD1Rx52kRy7jVh z?FGI|;?Os)?B2-<|Bd+G?h+NpdXhyRw~XHKmDo+>s5sydBkZ+e(w3IQUG5=NbTF3D z+$-1HN`_+V`r3mL!AmYKiaQwz2SVIddq~?G*;?dP9Kh^2748-86`;hW_7IIyaJ@ZF z&8mLImuj^m_7WjgKDpvR_~|%$K?{yM7oLsA{@uKV7nGzD@?1dh0W44 z%SB)OY)%~W2BLOce)ffck8dpmjLdeIuC?ZuKzm&zq2qt3TKOjl0M#&sd}yq)$$0oj zx2z9Thsh#KwY#qxC@V`_xS|D3ccratCPH+ZR1IxarHVneVSU2mAoj!RJCo-AM^MM^ zanVZjesXuda>{}VQd?hC2!cJ99&!rdvLGxYRAz>v=m7(A6nPs26 zne`rD57s){az41{jEYx4_$u)XhQhZhSNaU6mg?3})+_te)+C?ydn3(1Jo$iDqK|U3 zP!b|gmEm@Uf|J-SPH7A2{4%^#v?^U920hRuuPJzs^O}cxZ~O|2jHO3z=`Ioxp}QxGF}ww({ds5Y@oE=6Ouo7d0IAV08FLS1@lf?8$0AJ-q4UWN(E4s| zayCTh&UQOo)QkjacC}!GHR~a(D9yr8J<|ZJ_KK!sq9HQl$Lw%T3olEd$(bgtl_AU( zDX?%}<$-!!e|AT1UFbk^dDF?2vHji?HM=w;f0041TDEB>}dg5~|9=Dz)8{i{x&d*Lg_>k~u4!B>JSYMtrAn;=%5v zN##sdB)dCmALKg1{xyv57)nb?i4lE1c%E1leTx2R=;8E2rqTX_Rvf$B5VNuF3ec+S_n4?>z@v0`Ooy zf@G}Z0^Z%0KlC8k$H>#|mPt6Mucj~}Vo?5!i@qU(&ZV`_?as%8uX}X$5UFN|xVMke z{6sNgLt7`}WZ99>DVS?=6gB*uvD|3Dkxq}eT;;QraR}KSLwr)k9pqm7OJ-~i5T6fK zu?a=o5P5<~@wq<5;|m)Vk1x35q5sqd21qH{Jut{>a?ikq0R{&iJW$KtIzRyN0oDD~ z0)o5*#6Ng%EqjmJ@2#}wwBJ|DUb2n$`zq}~^%Wa!Xl+~e`=;=Wj0|O8+r8n+f*aHc z{QBaK2D@6?;U`i&Ng^0=rz+K@s(aJ+w5xiZ5g?PHpGOnQIP8Fd@FpSdo8}Z8N8Kz zor7b#RZ@opCpzo?h%rF2$~Y5{1PEpcwO{RkUQO?g)fF7tg*T{@pNCGu8Q{UNCgth! zftBVbiBr`ihT)#N{B#O>Q4RN1-F<;&!+E##?Vb#-UbGA@OBsjJPJxgCsTk*_Lr#%; z=x(1lhWxsbBZV*-7<0Qz)v4Z-O_-w!h+UF#my9B5U>7YUY{MrHP!t--rDvMuFZ2}y zkudb=T6p(XU$S?L6BiaOoJq?T-2P~uAJpj)zUEvE0Z)Ck;#!o2p}H5Gp=eB|q` z2PmX3$%g~q!Q$MXX2_^KgLhoE^k!rtH0XDg#yzLA( zmYf*35{(jV?x8;`=Vu1A^1^SoMxi112;eIPz93Q!)UNwq6$vUh84MeW(WA|h&SL34 zR#xm-*dUN_K)BB1;+#SAp(Mw`53#3Z(jF;(` z~JzL&+5*n9G{kW+e{S!gu09gj^zd%3Pni=Mw|`_M6%SH5G% zNEsiTFAVI_tId4JASb~XY??o^T7Bo>ZG*QD-ZA*x!Mg_U8QeFR?Hn9Ba7hJaX1wg* zD1%BZ;`o}jF*)HEXB5YTV_7o63A4vi*Hhs};(?*mbC%t z6t>#I++VbHFXP_J3Tm=r{UPgP`g(s4cWm9mLGK}C101l=U*@R+Vfg26{qYh17#)}p zauQg5^waUtI;drL4(5ZDF(xr*)9oGPq_N-gyjDMn&q|%KJ$t<$goOcFJHsO~0*~9` z;|uI@dwe`-kG1%Czdi1Vk3VXUx5UR6+T*S9@sK?Z#K#xe5E12)T4&R>g+2OmRe0KQ$ zZ29c){XqHb@C}#G4&QUjXNT_x%V&r0hstM%Z*TeR@ZA}oU*PclC*`xl_vgxIhp%2f zJA8MQ&ko<+<+H>0!{xKXH&Q-3eD{>k4&R?IpB=s*iO&ZezR~j8;oDa}JABV8pB=ux zP(C|+W975Mx4(RL_=@t`;d_4h?C>2ZpB=t?0((>8i`?2!b;TtcX z9lnY3+2K1{K0AEJ%4dgfvV3;qR0I(!e5&koB z&ko@cd>kS_-3<_$e$g+R%&nv+o{1pyp$Ro#>=U}ft*VX4rM1bIGC?V4G!m()Zl>5 zrv`^~p=`LAa7dR^gG2h-)ZmbIQ-ed=OAQX`N@{ROS5t#SdNnmTq}R%Z zaHw2Q4G!sAYH&z@A~iUqerWshxB!+!6E(6QiDVKms5j7`dDgkNMBzzgyZEu zPYn*~e~}s-(l?|AhxCo9!6E%Cslg%rtEs^ueN$?1NZ*_q9MZp*8XVG}EE~c>^On@$ zkiIoFIHdn&YH&#ZdTMY;A5RSq>Dy9+L;6#x!6E${slg$Adunh<-%&P%Bj<0X28Z;g zQ-eeL&eY(LzAH62q<3@?N9MY#!gG2hE)Zmc*qtxJ#{^Qi(kbXEdIHbQ;HiV<;zfBDe>3^3R9MWG;4G!sV zqy~rdH&cT{`cG1WL;8`_;E;YaH8`aIG&MM+|9xz@?|^gukEI5O^y8_)A^m5m!6E&v z)ZmbQA~iUqpG*x7>Hm-#9MXTD8XVH6Q-eeLsj?xQQNNuU9MXT08XVG3rv``gGpWHL z{U1|m`oE+GhxB(+gG2ho z)ZmbQDK$8xzndBy(%(xB4(XRugG2h2vLT#N|7&V+NdLFg;E;YbH8`ZdpBfy}f0Y^> z(tn*A9MV5X4G!toQiDVKZ&HIp`oEVA;f(sj)ZmbQJvBI_|3_+YNdIkWa7e$A8XVGZ zrUr-f|4a=I=^v#AhxA*i!6E&2*$~dC|0^{(r2ltna7e$C8XVH^rUr-f-=zkJ^pCUb zuB~k!V{ZGv7Bt%)HM0p8pxJh?d3=|O3S7t)vTX0!x|;*ujhmClte;o5JD2DDqGzs9 z9vqkpZY$NB?#bVyy5r3?xpf=I=Hx<{PQP#+u6_t$d}7@lZQ1_8fkRhTN$_fxR<`qg z9{?7Y?DNA@cE3;_{yQE+fb+bzEgRH5*#BYwseRR=8T>NIcz+VnNmLB9AWRwd%@w}V znI#64hw#$L$>LCH`NOJhMSN;m?Wy{-YU5+x4~9Y&?Bdrl2(mAv0=q9J(ks5;n3x({ z&93$)gEM{{jQ@g8CTDkOj!(?bmmu|YF$ezZjLX9VNb~K*%Q!hkF$iB=qtM$vg9$rj z6>eya;XPpbXEkab&4L6Cp+rSzy z0aM4kI9P(OLLk=p7&CZDUN3(LX+v@5eSpgp&%&ZZ_+P+=P@t|D zK7JLbdU-aEQSynpKjHi#Kb|U?e$nX09nS8|>u1hRA3tpC!ix)Xi0BF+g~2GYBiy$l zH1TjDzmo+o5FS4jHdYQGQ65j%PpUeeoCfB8(>2{}$tB{c-N!@KQcq09ebNlm&-Sz9 z1~*YKSMf4=ECS!{SAas7s0dX)t{+{xZusB!ciCgPST2)+yt=Y~=~8(4JN4HnN*lD- zWoG6Iv_uKrA#Bi^^KHSSz2ygVj|l}p?Vgfi)Ic#BFEIu(hb&T7X@DSn*>K>wH?KkL zG-0P>)-b)a+?nl(m5sh}TP+*CCl^^P3%|H_{92m?UT!GUOZ8E1vB8zV*0D!m8?0RD zvd`9@OlZQYp?#Lju)E!RyZ&AW#E}w;2uAjbybhBHV|XV$U*hOG1X_kyWD8zqu6c9c z6NJ+y>CqgbY^@N+;8ZMFr*}LD35m$!zF{34`)B8s9%SS_4@}mR;TTbMptU86mqS<) z94$dv3`BGMa=Bg13rw=_E>RnI&IY#|Q`3xX9pq(Ysk75g%9?g50sHIihi8gR;s3^7F=OGD1YKugm1Xu=y)1Lc$oJJ^4Fl?zHJtsbCH$1rVH6_#F1+Br z^X*H4EFNb_Eqr$s>bCVW!YvC9!J;f(ps}Kjh~G8DRT^B~!EJ2Z!47d!wEt3*f|R)6 zFKK0pMjCLMV*c~(_-&psCLsmSJZ8uuQqVD+C{(XkG6>5IG*;uc5xb3$oY)8o=Y4zD z!hO%N!2~qCP7Cb^b0{mq7BB0!$f}*2=WpAVpBg@3nCC}*?>*bzm=8Sh1FtoKrvBsa z`xJk?>02W^bR6Z^|K&e=+e0+u**pK}rH|MjYUiKy%FJ!w`GoxvKF+f@oqvrga>ZBp zv-Wh|#;pJObRfpSf!<;2RRgGziO$THmoVrBkp%GpVd0mz9UwG_uFl#2YEK&5i?NMC zpa6RUXR3JVL!BOeEj26-t#N9J zzvE4ZzOOD_X%~BgKl9hN{mk}ZmtPMOrHIRMzdsQsl>NBUC-`UlcV6>bbMLyf@waO{ zyzuva`a6%l?VDc~OymFV@Tc={z2k3Dwl4ea55M!L|IO2X{f_PY{YO9fBR~0-A3J_Z zo&3_+wx9mlmoB_Sf1mtUzw|@Xe{Jvs`u1&~`JLep{PsT=USwZ;*GJ#~5B}oUzNv42 z>!028dw*xo5C2uYKlc-#{_%hDm$(0>-ud|>JG%S++`s-w{r%yedGq6EU;R@*roUhK zv48xWbMO1vC-wKg{o_5K`T6t9uhQQ?@OM9aTXyjme?@=a#{aU~8|p6$cQx8kjwlr+ zkKpSqMyICkKXk6!zSOyXG9TEM?Q#52sDZ)_*&n^YUg-^|2?*W=1454M@ZbdI-e#3~ z7L&WX*Sr7F115goEuo8zA&V)*gv3||@4@mX3wTMh2ibLc_liY_jHI}OK}u1Pbs{g2 z3T%)753JiSC;+$YazN#A=_nyIp|=c4aQiBZ+rr@@K|AdEC%0uoN1GShx&8n6=<(&H z**QBF@@%B8)zd$<>H4t{K#jb`(Pf8qF&4A5xqK;ialK3Z1 zcr1ao!lybGX0T!O%H#}!5F9>w9#*onpalOst33voH~eSR1u2IWB{F=xDEGX& zy;5Xj^&hTJ!G=Y8*^Yohr!P!ROJ3EvXn`?O4=y(sE~-L35;97F#m4A&$)38_%H^u& zx7hmm&aBDa7N4Ta6m+U<2|ypRY7|pdNQ^x_@v3qJ-XIBZ?T(y)-Zk!+Ax>_Y4G_+* zEO9AAZ>-M6O_F}bkT^N5k#K-Rf~NwarG5zG4PQGwx*47CT(LvGyCw=$`a9}anu*)Lt z($)8dg0+Y1`p6Nio^Vi?Ra__Y%Zi?e5(BL^s;^fGl^PD0W%>wwz;(-!N9ze32#(ks z2_clz47p+1x9YDXuCK}FBmAqvBAN@SltCegk~nq;g>%J|+Hy^FLKk_i?=2MFaMp%k zbJ7r%ISH|Q4yB1bT!FE&^!9q=!kNkA=Vy+cJu@|Z{=%{8lV{J&OdWsO>EmanXHFeI z^Rnp^jUxMl`dI~T5rys@NOi(?BrIkFToxFU0iHz3v1{N@RL5m1hm1}5*g0Bm)prh$mo_%P)>#mLJ zhM25r2fB*<5`?Ckq*vMq*+!r?3Fg7gb8E%e$+Fa6E6cu3&jT#;G+~XXMQER{TZ~c) z7oFdVa60F_L_90?0Zo+O7Z@^qHgun91C<(?mIH0Szg(6Rbje>jLyU;Ch{aI5UaTxI z!Jr+Va-1Zdxs<#H?$~y024ytdbO8S{GG|0xEoErzOI%i2GOfg*MI9GcAqu+7ad>}TkLSxC=>jlqpOdpb zq}@KfMq+MUOy?cO1uZk6-5L{y>f#du+Bq>BXZGJ~y3!v0Y!uAJ(F#m9rPUFi$pLaH zNLa{gv~5&DaY$kZh2RpY6`x_I$Q}0@2p66Ka{wsBPL|C zdN)bA(*CJa2u~ATUA{e+smWifQeRQG#0sIbQFdgbJ{8lsMa zAUX!(S?jd1VCl5OmXl63Vk03tBFzo+z~9O#Cc-s2swx7qYjYk!E1sr3X<)TS1!vOi zxFJ)lGOCRnu}FBzCi`i$ap?Uw8rt};AqdHCK2ESxIH*o`vYCNhm{`7AA6;8oRTMEn z>GSitYQ)%bWmwoKj8`T;BUHGxjB5Le97SgEpuIR5ZtWeZKP07ue+K`+)+bf#R2IsZ zdI(r*f{>1ra0-XT7u6RqBd5D#zK~F+uaw{pWjK&P z)faVLdqTAYLl)AomL#yb+(FIjD*?M;lC5xLPaY4~BOR5Zs=DAK_2nP%sqQhE6bQmA zb?ldB0CODG#C60uSstA@dORW;s9@FSfdZtqx1l$uaeGfR(xuj*Zd^4ZU0MaKMOUdj zqeUq;;*1Vyh-+gD9Gx|B#*h# zvE9n|ZO=!v3vB4Za^=r~!81$O`V_LqOFhdrVm*o4suv@Zoz{hAC90Z@srEta?>={Z zWP?DJl>M-G0wJ(F_VthD`?qJcgQxu_Q}RW~_8zG}MTU?58Ip|*-WyU-V~TSW_TsJ| zZBw}yoQ~>&_(HmFkX_bBJTGqQ^EmhrD;Ow#5t)0!**fww`I=;z35k^40dl=cRmX$y z4?{o)=<$2FyIur9#})!_3Z$xb#A5+?fFjzAT`2U#-9Rl`siJP_2~t%iphW0Bobwmk zPe5g=cMZToYX=@i9;|;77etP|BHOkXaau*z^7u!s9t?VYL*KUwgmx5j-PNVG@Q&Y~|W(juI0e z$-OV~iS66#p=ePrFaO$e(42oK$uLsEmpmIDuMVNVafJy~D^iQJr6bW2k^&lWMw~if9Nsw+zULOJYI?-s!{;2MyOkHhCnu40`udPx@km&M zm%encUVq(?rXg_zYOs^f{Ao#_+H@uS@PpmCyPAwCoA!UO1zOo^c?>xc*!Xjfno$cX zVk1z-yq=8INF~3b+JsFFTNpRigjR8pKt}G+D68Io*&2>R zVFe%bq?|l!^8MhWk-StbrJAx9vG)mB1Z%^;($`p>tM?TLiUUTxx&eC;qXefwugKfm z^OeC9Rv8zO8rh@ds~4X5P9L+XecJd=Jn@1IsJf<>j>aHn*N3&n)l|t*Dr+^b+g=O} z-3PB5_=fkg!FS*rVmllJ8LI%RrAb{DpH~4kFjIG!AV0Ph;eiDZ4HW7zR1l*N9s-W| zj)#8~V?Yu2!2zjZAQK;5*Z_%KX_dV(kR(ZO22bAEUOTZdXeQsJZD3Mns0Z3@OZJu# zjJSn`?c-V2`g_0f55IU@c@S*a^VhQHIFTOZid9ZnlZ2b|eH{(dc1)uxPAJimY93@z zS^u`*)sGrXg0)lLn;rb(`4r5o-+i^ywVNMTI?HzpUoI};m6UYS!K%{WQUXqtm9>WZ zWI-#}>iL7g_(pg0VRaNcX_)pl10Y87OBc-&&|E)+BqBbSzS;c-mP)ENXk093on_NC zQKksCf%>WAWC1$~3b^~cf_SVJRe>aSlKU)CB!e9mzH>&7hj{?N3K#;NTp8+Onb?n0 zL4qqcI1&gnzqVg72(5bGI>C_2l|m#|PB9){VA}_(=^A9++~-6tfPFdo>ceO)Dyv## zM|3`QaY2vaQ{j*tuRs6!X8w)Pz6INx{9(Sp{J_&T?M)dvKVfQ|xnjwyNV`Z`~h<{cUKS#ul=+EY=#d&lpos=C(D}UWr{i&dVRJ zzjDqZy^uuJF_S@pO*P4QGXr&P=URKsv7IIJ=gpeD<2We)yw!{0U-Us?rfJ;V*`jAcDg zzr2bHiOpPomIpC(Bl<02h)1d~} zL*Y0!9Fm4L=Vly}k65%(2*4G4og4`zNVV_2A9B2!X5hhK2!kfp_(7=qdy&$5#qd#; zi{Z+*+79CjxdJbOZj7#CGA5tg&DQkh?vApTA-z}}ONdz-Rj!P9Eymf7GfTj!U03g$ zs<(Ib8#n?^UgUL?5AZ6;0LIe-mj2S(&rq&wGU+gajaTBvf3)$+oiAjwIi?G%?cT6F zdyB<}4t~|eJ-6r7lL?cFgjZqpNF`-QXktixFwY`;tbP);+C^!6TRM6?%EpC4cIlMd zrj$A+y7{f*>BrY?(u9%7ok8F^_RET0?QGW6zSe*DJ46yH;|7mh^KW#N8}p4h6{wy zh<#~A5WM*uPHFTY^il){Fo6H&hU{NJ6?TKCw7U;1bxQ<*UPG|%`@^rsbk&)v?JCw} z*1jS%75r52A|RbWeotN4hZYn)cyEN-tF)Dy=bc2BqGOAQKKLlnjbdO@g35P|SYCq{ zyvUx`nu>38An?0_;Jn-io1b|1^`1DJoH8Zyri{*TP6!JvNN9IvM zM_(+R3F@;PgV#8jFU!~GhAl8tC#OD0NIqYY5Daf^MQkODjFC^_Mw%y@1>cwI@k<=1 zpUO^9V@k(T-(DF+$P(~!ws&?8Tmy>E0!n@d>|iZ%xjZE-hdThWUYM~EcMzOXe#=dQ zl?~4(=3Imu_0u?0`v@ov+&?a;$^L_~E&0;-wkiwsJR z7AI|q__$&_u{~?cd68;<)xdK?s%-D3u%;Q;GqY=*d%n0D&weTS-DARa^Es3u(?J+P zj)T$)>JLj^HXwfi);siBgEGA2K$?^$Nzyu+Blc8=Q#=5M{TyrJoyd?7eO3GhZ8aVB z;V!N)b&$qSTrr9Vqs(bUX-LU;`XeSaMznS1qK-3T*2sdMoX;gVeS7Qct17Sl|l zHzjz=7gZyeV`XTFsom=!M$I8iA-W+WtTvzWSViiESDZLGHGTH{t7ayTpCbxL;Gv*4 z{j^Yah^9&%P>iDL#d^>|8Aoy+8=rh!JcU@rn5U2DE9A?*?+?r2H43yz(rEXc8Wi~Xps%h)^X zq4gQo$1t3Z`8SxqBs{#jQRyD0r^7Mnd?n{mNnBIF^f}>Ds|vm3bAbPRy+KAs5)YQT z=S?7Ox6ZYb=!Z_PWC*Y^tW9wz;gS+p4ja;Wuv5caYT;Po`UuNvFwBlC!>Z|=m+ir# z9a{SeS(SZW(H}66dCw!zI?q?S0HeYLY@BjN=&^)JK>663;`b46ct2P)|38j4C3`xD ztRBfp691mtbHWnL2&!%DLjoO|tfwc$o1t@dAm=AEciOHAtfN$Ahj|FEIr9*0F5GTz_`7XfqqkfW_7! zkSs_c7JM##U>Wm@l2C04kbYJ)jt(shUNW3p#2~SUe<0!41{6`suhAsT5&I7Td$XPH z?CJs=Xezwdp-ea3>?<%52Wz(hi@8mx`bn+%M=S+#HU1u^ALv7Rok??!77_@%6x}pi zmhddklsP#B`c6oJu-r+D_b_E}zwAt`B%4Wp?i}!=yk7#bEsKZ$$J_uL)j*E=QtC7E zw$9=ifc^7w`|yX6N?c$GHeOS;K%JQwF7@Id4!W85RLNj$HBK%v6$;c!K=}+yGH~3} zGHn+mJ=sQ>o4I5ZDc`75s8nwe8y!8n2V7Rri1Ky&NZ4TS6iYfE7%sMuLW;;K|~ZfCm*V4vhcP>;r(M z_-ib-h8x6NM`hfq6~q}~f0#FA-ar;gDgFczEic?WqJ_$_7;wtvB*zk5AAE)D%}N%w zRW`n4`YZYZ30zt}*c3@(A~dZ=qiP7U*S+d^xthKD^HHY~BQ=X5>k~&{b{M1N?u^JP z@{t`N=~mUwDqZbEEyB3Rq5r^A_jwH~W3Gh{F<$GtjaFC&f(`O1irDk)^9J$UU*DcT zHmsQ3a8_`p^oE9p;kE?C(%e~CU8sN*CZx9%&zYcNx1Htm&b7P8yUn2?Wxv*zE8fPy_05+n-|$)>ttds3_@uZJc&RIT1fv)af`qtNQwQOsoDQka zAEM8pJm0)iy${b2cOod2yV_W#i`Wodf7ep3a%lt8EwbAJqe?6a_o4yk+)r=_HKJ@# z5Rz!oRR?5|H15(?24MO=1`1THjfBV%O~RSN{m?q%6*{_R_@yi+t_H+l3^0$S4clCq z#)*Opdt{?Fto=0LRufdsKxmt}*gVaBNWOMN4=gDE{8D zi`B7o`>`4r@1I0Lm;2{}InY@DV{@yES3u!9+dgpG347xLG~D6F`wfh~`T~ZG`OQ7p zr_5a^4+exU)vTx=PX_#Nfhz0;P8~4EgaetI*(oGiaX)os*xH5QEFnh~RUA3+BIP@4 zx73ZtE8MyKYjE#*(;iG*8ojNCxWoZ7%_)InjT8uw zF8){Yn|pRcQ8>t-*yz4m%#*TG*VDy2uflhVOp#V&C3!?tv|#F7y*vKbS? zCq)t1NwM)$QF1u{f`P`qL4*kcf@Q18s^sts=D>I@P}#(1m~+%R__KlA|H$g0m*Utp zvjB^j~f0&`6Gx@C3*L_ zd)6f~-PKj8nlq9>co7n2?fD^7sbnADM)D z;IoOa0j<*h7M3}!9Wj6RonmG0wms=v=yGS8l@mv?2xqSmyrWO|^I=)F3tKPp2Dk`k zXs@|A#UGM=g*y#CoTkjnNkn8~8EGNx0HaUK-p=o@Qk-7g8u3B8u>OnFa733PrlfU73*|Yp^#+Hz%+baU*b+ z06)?^2a;YbNgmncB%wVcbz>&{pVio5%1Qk6QF`@JyYE*z9*3Tt1FSm?8>-!m(2;O; z1ye@}Y$Er4ABhBfH>h&AmNx7`+IL7)cQ7jpNlB2T*aO{^Q=~9qMBXjr>2=m1sxfFU z#RznVcd(VfPIRNZ@8I(mWZUOtgylZ3Bu~jYZU7@QXO6%`FVtXhktO_GPnbQmF3}vY zOa&8Tx5BvT$Jwc*U%v@ z{O+z?Y&TaXi>q-)@S!Ek=p;h6r3!K0jrrLsSUnP#qsU}v^Ygnu zAz$KPWNDz3*4d6gR@$f*fybnPAk!+F^nR`cTUxz5=Vl5Gx6o-}=ZqT?yGvs7H18;s z&`4RSjq(p%YvP|_o1W|GoQXARmp$Q4l;Eon><^Jz!w}G1(13yB$+Qk0R<6_m5wK(o;DMi;}6BBiN`Cc=w3=!CB-G8e@u|1Up5gSEIq%QkNS``@ zOQj+1w$UWYjZRd`JK_8eR$a(6IlZCV;Mr4jGe@MJSC&3dZ@iMaE3D|*c3OGJP;Q2`XkHbGhe+YMry1F5PX=GNosti+SwN^t9>w?Ld zSu|WlKWxN)2uXSjIF33AgkpXkqHCEGp@@=_v)YRvM78#m4PN2Ez`|opWb;w;U#;yv z;p>f>inKGUAfGimQU*sR@rBP~I$O)rp9hbev(h%_AP}=bM7CD*%%61njC%cC|Je4aZiXD^w{@Hgmv@C_`tDD>Lg5-$;t?eUvnHjylPxyT4KNkvMgnwJQ1 zgRlu7URRjJgiH^0Cr2OIEFoe}0vM?&a zmhL%Mc!JJsW{A=qp>#i0-(eCYDsMw)iN}f=O2cLJEl5E^QCt7;3?`W^~h(qNWcUVOQ2*UU?CfK7`#6p0I8zCPD!pl2^yVGn$)qFrL0rvOvI&j|)3KhS{aMqAOF0!3Wrh+OH4;*()VXfHfs7SiJ!DP7P4uf>N`@4_5OIAb+ z;U>_C0+WlE&t8(-B>PP5vr(}7-u^rE2%-Sm&Q&yISDfSoS3JKl3pIC({!&CSbyj?( ze^b2^J4vp!={R_~Wu*XtYy&qTzdq+ND6asV%bw09wM-Zp^f5~eD>7oYH?Ce|!n2tX z*nA1SfLA0SHiB|F#q=q>GSlXYcxAJ9oEzA>)b??u7J0L()M6^^Lo|vCO178xzymjB zbLTj>9>`h^r7jTlA$e4m&62eD7(pVSNFSP9z8{Aux8=BQ|%8Sy|Xkr+IG@9^s&FX4aQ!c0rarnJN zYc)1qMH7@8$Vt6+rq3E$4v33@aE#T9#09aDo^(+e z6NY~9nEA=)9Tg$7TSgz}oWWd%JQSeh%q(HraA48FXq|= z8xP~qX!a5|YU3Lqb=4-$Hy$4+p0m(vbNP1AA~itpg+#@G`0@`5;cWzr24bnm|9X=G zyEYUWh|mnR1{PNwIMr1ASX`lBe={{ozHBv+mQcnPy#lHUnET8K5~Pesv`rgf-ujTz z{h-aKueYbR=#6<5F%`d%dNe6}DL6=OKI6vZUMY!Ky0oiQd*s?+QmmS5(M(#)5EXO4O5pM6z+ z!}2)?)~RAB%>L>Gnw@(>LIc1BH>#*cm|a@v-3Y?TH2r`MJvp^OstP_0+=}<@56XNz zc0}I?l!`OcN)SUu5BuFO9&vtk0S-yMGwkdUL5*yfus;ilg0|$OsD%vYW#Th^{5YgU z=wx*hhjSr62Z~csQa;K(2PLX}@bju9kp2;a7OJE;sVJ}*_Jt_yC=fFe6Gn@JdO-9j zSHp@(VL)cVhQp$@aWG+a%Z5JSSh&H#=Gbg&1yaPiDxXHQLSZrdWS`x=DC?f8eIpga z;o#LE0g1^Yh&$B4x|o^&SqRt6>QZ@)CwXjSt#c_*QWM(9Z>K z9>Cwa%E5ZANnFC!NIO;K%8)24-%62xw|3iJsQ|N``8@tHj2h!w6#7`cgLBduRnT8p zT4*Uoj6^c*alJzL%B1OAMtA7TPf!ZTT;uV*K+i5nt4B-v=qO1u*no;*M{N@)J~u!o zAC!O$hfyGR5W!=2;cF2wrBCFH!NeQj=vQq{)MGm&=bGo<({4PpMAO=f9M_^HDjF8U zbSW4+?`c31ei@E`9kJ-ml%7$$!vGtC?fyQp`f&cW!8B%oaZq?HcOZ<y_)_%kP_+p}|v@-iJ$<|DFD zJKv-OHhBmY*Ge`VnN#?Q@>G>Pzc+z^p~*9|Ru5=Q?%8#}mS zSQjxGT}%~tqsd$r@{`>7jlXhnR$yu}FBosbWE7nbu(-4Dr- zk{m5k+KF;NQTNyOuPztLym{s^a0Of;t(uSqq)3SYJt~vdFW`!BIT5$2us#58{=E-- zE6HXL-dU~3Sw_TQ0uDQPH!UZ@Hg z$Q2UwiK&0yMs|dUOX@K}40eFSkCWzUxGUjc9Ou5Es<}l*z2pO~yvJv55(c6UzMwc} zk)bEZ-HKp7kAQ-VK?mZpK??ew_v%nYlJl~+GH18F*i}Px>w0>!*cq$A_OAdPJ1$IY zkKzZRZ`Wg46alrm%<(7rLK$;`Tbt?P>jQ5{hxZnL4L%jPxt1wNn66RS9&a#MWFL73 z6M?ODsPT=&gwZd*V@Lj-k@`909yz04w2dntNI_9w#Xx!bQq4l;64-YsI7Zw5_cnFT{yyrhMvO+eKBkC z0M~@I_r-Ylq-0~^qwAIUvcS7$*fhSA!=pGbjfkIO(#8)_Efqc2it4Inyn#n-rO~W{ zID>$|F>YZ-!my3bVVS}#j9-#;VCb0Hj0T;3MJ429S)f40ZON()^AW=f^fBqd0t!y>@5C7=~x_jRWDH?WoSY-nicN6-hh3b=!?6i5m>m>zYL z^9MAqE#!QBv-t!B&?Kl^6;`MCY@8+fw`ICjCh4f;c7>p$d&#L*k$-bw=u{~H2C7hX zqRkoylAQ2u34kB#JIDj!@G)*!)188LYCMdkB&e!Sx_xio=T#xs#B&)TP4z%ny>myt zlLPp>1Gi(KrJlfQcMWzB{uCbS!yQ={?&7;bScRJ6Eqid_YkkANUE|C41n(J zY^!`T5zHZ+k~{Rlx5^LLYY(Ge_njM1SP4t0!~&@tv8FP+5H<3#nN}~ z96I+;2-JW%$8;qZY2tJ^3!E5iq{O=weCWtq9v~*s7%s<;3s~xeiE57Ov0WZ*LqCoU z6?u|W({0O+NtfkIR$~i>FP+>MPY?$y4(HH2-}}buIi%3W@MA?Uzm!CW1iZ^$PB8J< z5<}-I1xXLdLb+~e=RUXXoXqRLa?cMAz?b4bHh5r8$tdur9DHnwa)!!hHH+$FudtfN z!R`^Qslh-d=1Ki|g~$&bC6bDLeOi_mHh>Q*`DUb|zKPUmK5QF0@Ez2%GLH{ioh_D zq<8MZlntT8bT@$vVxE}RNwI<5M+;$~GPplhETopeL=9?kDH4cw8F z2$k)b)?J>P<=ACh2t~lg;$?sNeykBg*I`}OR)%y1bA-h-7lesFU=b|Q$^`skxqV)( zbp(n6?{0Z)*d~B|IZE=CPRcZ-L>sYckejR|WfoqZt?Kd60%C9TlPe4mZS}~^ls4Eq z#Wt6de2GjhzixZV&$z{-w38N4*FwJm^ottqriC&ayAq2r!FmLFzu zoMq^?anS~Q7(tdHSR%OUEQU+Wl@~+CQ22CZsmSlyk>8%zhUt;}6UH8Ul+4Kb%coAW zx^7s0xm`CKJ3pM|&)xZ)y%#7Rqbr2+i5>Z!dF|9imFsA?R5NkDH)Chx(9qw19@O`w z&Dc*%a7bI01k>;tasi)l0PP$OD!>XSk1GQ!YeS>)FLwoJcX1F55{6Lq1=|`5zq#=W z$<7VI_#QyzP)$&O67wJ8Vj-H+e8{FGKj+z(*MIl1choCxPS;^@sLGLII?UcFrBE3( z+p=%6uki{4ORd`P!-i7rj580pQT9vx>25TPyJO6*jMAI}vVEJDAoifLES{`jfiaF|lQ=+}5s_Hwb^D2At2FZO~@``(?C zk%gE4-9zQxt-!WWV@ZFEU#{Vu&aFJitQzzY&K83KJF4l?`mhoSFPsP+5uy7%@5bnP zLW>FFZ>ku~i@=RoCSHC3#8eN}(9cBBvR=gT_$dp5E3B-v_G0E2GmSB6p*-rGh27Pg zPwG5Csl(pkz6rZ>N|pmQ$c(fIxib54aZ50bTT}*o*(ixgt9OKbOoisBXq15C5b=<# z5a))tK?8bb|DndD$n_3#*yd?szWr`fhbdvPxDgkah7&5<2lV9jllG z=_cG}Uye9;^pUq#M;r{+*eyof&^dS>aS%vZO91!y0x%3Edf@IVF zDB8o_0a1gt%N){TJ)>8FMc5w+$Q2>UkUHa|Jq15caF(2ss8Q~jZGY&0fc4?Wb(v}F z_cur#awjq4pn|}wSRMo!9OiAw#9+K7b=!>XV3vUK=dX2WGz& zxTh>4rU;j00-!3^pb!QCU04TcqVsYMqDW+Q-!}(N&Z2oVpp~^pVHQ}^ipvGEn=Yr2 z5cQf>Dh%qV=6E`}^SOJcu1lXr=>f+$cZ`H zP6K;n(Sz~z#rV7^2`znEWMUe6-`y^u&{u$oLJzAZY?8E6_94|SdzD6W19TXKn=vGr zgBfYKV(^@r3>l?|-Qoz`Ocm^mKDgg*ep#SL70Gndlt5H|V0&R-d~M zR#{JXsAE&Z%uUS;%IC%o0z4prLDR7QQ!bVFDf)%Qg?%bL1ilCheOLPU(5ZvSM8ZMm zuH+rJh3zMy;+-cG&I(OXjj!?$IFE&&+83x$9L@Wb@3wREr`NL$@%^81}W`$ zkZOyi^TaEu4u5%ygJ4p{tt3DVkFS>sWSh}~2fPXBy}D=^6Us*zGMP~YHbX~}a$Mgq z*R1{AM$P3|Bp6n<;Lhx?lOx!+NL1mEa9_f-8$E1X4w zEa@~_aLKm;C3H|)=Y1V<5wn%iGl&2Dya$PkG+aMxzB@v=k5iU)Qv?^k1qNJlQ{uDD zGBPFRIz0aP=f3~8H&mplb>bV#rXtJeL1-ejbV2PmD>?E4HWm;)tO&1CZNKgGFx^=$ z4#i9gA9HBP!cefT^v^$pqD13N$?u;Z5EauUmUQ=!2jdFYvw`fV(6f^%KRffaj4M0T zH~4NxeN1hMho>_f^F|a$jzv=IgYM}Y16Kj5BvtYY0j9+KlQ{FQwEul`=K`N+S>FHW zfl$&mXq19w7WaZhQcNDw15it=P1BY}TN>H`Md z9H=;SsOa!Cox{^Spc~G^2hXs9tDm7V=K-Dm{eOSgb>GkPyh+*#o1g#sfxb=N=eZBp zeVtEtYpcLY5>I(O#hK@u45CwIV&178sCwKWORt&zI~8R#%HHtYM*XX$J^W6N6_cJ5 zVp6jmtU)@EVx-qR3Dp%jsZSP5uu5+qRfA8@vH|$dv(|_QOo|qEqai31^{H-{YtWGA-!!H<(6_B27h!+{9*Yu2`2ae~&HW;`Oo-Jld;;=kNmrPb#edV{6XU{9DKF;u_3Qe8r6rhXdT)wV|7D)-2@$J?st zHmkg=dbV*rhRH}Y_hJz64ZW8S#C_^>UZc?m{HJbHNL^ZCfIF_R88*Cf3R32mnKbN} z?ZRQJVG~_`!aaj=troe<&iHpy-VmXjdat4}~ zzXoTq)K$B)Sm9s!Fg@;}tX{>R<XZt=X1a4WV!Z3$ zL#!^xeG4^OBcI}8@o5~&8ICKCxLPb`6ze9rTxK^Rtr68qvSHaVD73PFiI#=TelG2k zS+2tX3^8UiRA;Q9wBT!VAnQGt7&&QJRb`|vI}m0fOKg~k&NI;@6x+wGFw-#I;;5W{ zpL-p1hK_L1c?ZPH((7hFHTuxg_>boJ4`ZV*kr=tJ!AXjRTIA#*RV)~WgO zY4QpN`>*rm7eggSO_+xPnXgTu1P-v2SXUJHd7UyLn4w`}bx)d=cBIW~31}%YP#Yw* zcBD?p(+o)-!Ke$_+R?lS&&%P(3Cc>WRJCgy)wm07Ecyb%GBiGTx>Y`GS>)a^CrJ;_ zUa&C!ax5+W**w3Cbo{}7CDye5nE;I))PpO@PzBlO7vf4Znh%f#1goTM5IK4xAfe7! zhMK>LP0#%Xb|s^5bgAc4N=Q>#A8VtBT-O%+ClX++Y?MP~_u}Uuf@aX-0(D7h z=(SL4qoax6c-9FHBKVLMXF2AU9;u(9R%1idk6WzLibu-N!Xz4E3ij%Z-rH8h z9rdea^;b3|S#qRZGF$ZS03rAu$F+H%ysgKLOn zL|Q1KzdxN(hYBhbi<`i`WTDL{ek8+vuyCfnH=0DtmF~PtX3~$t46eUbs;usrhVGL) z)CDN>-XYv~fKx=5AedUJmFgNyGp@B_Xv6zVH9caGiZoy7Zh{U^<+_`D76O?^Atqfg z_@=^efgGogC!3v{IEK0E1tH&n>8?TmyQn9^Yhmy*xPeM`Rhfv6jqpfEF@wj8@N%&i zBxvoHq8?>PVFwmk$Drz{x30zEq^RorxS(&1qqh|Z8YQvYHV!5@P#2$H@e^5;4bHHw zh|hbzyWpg`UaTlq;wlF#ksE7# z^u_KV=Gdc9b_GbJLa8MV#{U7<2})O&6~^a`)@OT3$<0ZAM#g$5FpQ&Ff%2i6M2VO< zHGNgJWg@YHQOtyj>ivf%5^XqVbQon8@4;^ua3N@70Mz=pG|-yyn<)&q#9)twd1_*# zybzVY!NPjd=E13crDFL?1_=-5{3kdy<1(}KE3hW0pEeD#5X2C%(tbYfMWd}wMd6zq9VS^BJ5 z>80clJ~X>hiHSs5tY{8ODQa!Ee@!+P|J+^5AXLzh&>#GrgT}~p&2vgpU_PD{`nbTq({5IG+a@4|amp!?eHpOM;eEk5__Aul_#V7jD=|k< zOnHeEDh};KaWibuhQqF>J+A@&Ih3t9GmN&gfX6a%081NokBszRIm9CatIWav^Ff9vyUU zAOV8vZ+Fv5e9IpKS1u*|X=U*xE@dq`5ycUtagQ2akjB8JUh;3D`pN4^IE>S$^ukhf zrY^DH8|(B{rhYLrQq9&**-!sLrAlf~T^2x0bFd z-p2yXnRu*r$EMB+w9<&A!EU;P%#C0#>7ULQOx16r<8E_Lsw;g+Ba-50`VpdI z^lIvq^0oRF2(H^w7**4IA|WEj%->3vQY_kSBU~$x9(^ZMHc+3wV2zG~iz+J!HZDJ1 zH!JN==gIH+zmv)R|7QQ4psJDFAk9%*TUS$57Vl|=}#q*c_BT&VcnSY{h~M}>fq zbb92%Ou6xLgM$|srk*Whn)Tv`V|7>y4fE$US!_hk1=C)1bUH{%)xZ(W<8$sNmL$Y% zKp~fFu;K6$k7vEiu*KwE5T@>mo2Fql81PdCsrFiEm|BHTMREI_g?rY8@;Ll9`*BB) znbOt^lGoB>eAhN_iDEsRJxvtKvnyB_XgIk(384`keXcUrzfS=+_~>XqZU159bCyt zFv_Ntd6VdU0%ErO66*@Rb#!<_8%dH=^b|Y~q2%J$EV=MC(MBvrt!ON7XxJWGHajbe zx7)EWRFbeLx~XuX5RSa@Bsop|dSCtv<4qB%>^gIc#R;vaw^un(<)yDh9iIe2o6Pef51d3(smk|V4Rtm!fY!}I>Pg##tqL|{fFlZ8%#F#5f5H zIJlIhSr7Ez61UHu$#wqvn}<$YJJD;jQbQS$1Q?| zNzz~A^te;?YilMmeH~J6s_j3{=l7?zX<$jy{sY96=^2BDfg%H8$bVuGaGgpq|Lm%x zY%WdCf-2_|Wb7hCO&v|xXR$5eX0?qjp<@Jz?fe$uG{uHs22(Yw_RA_g+=u{}?1EtdGh4OU;$#`Y2g(t*@ z{pJLPMs$f0gGkZS__Fft?rHl06maaNmP%y;1Kc@04K^IJ`Hs%xL>7$G_0-ym=|mz7 z0y9Qb4WXMhgyX7Pb2<%p%R!lWXZSD$UF}?_EubnjV7T_Mdjz#`N(bNy)&bb^!}C*7 zcn1FpX>Oy)r5qBzumht9l>!m20hKE3o5wv*R|J+q#9?B5v?G^vn#vF<=pj%Ux#FCo zG@JV)FiEKm#$AjV6a9E;{hKVGc-RN)xlpn8!~Z;3BEw_YL);X_%AQjg{KnU zs!M694YpNwVe8X~!FrQ-yXhoO#4Xjt`)cDm!kjzuDR$WGRmuWprS$SyM*CYsbCc$+ zt$&VQgKIG+G)CCw4S!v{xBKaq-#Iw#oq$*VM&BBWBWh#*USCuyZ)ladmfy?Lm!_sP%*>Mx%C*48Hjp4Phzdr)815Ta1X1KQJI&0Zl%XpAnf zS<)Z6S|99}Z3kzymZMO{d7@c{*jv+Od#>q?tC<*>c3$vixnH z#tvO=@)?m<5$q(yau8#EbQbf}iG>xey4RoYHl5`}kSA4y<>#1XsV%Pemek&Gbnz*= zsc?9KyD21*^Ce=hLZ$^&2~6`d1WYV3a^HQ<%XNc^Y3QU+NrjMdDureLC|TeAgOcLn zBF6F17>w%{>Vc&NA&g^l2H9Uhdls{o;sJfrvkWak(zK+M5F>P~EH)Atto0YyT7tYp zAlvlrJy1<0F6$phFI@4JE}WXSK4sSJ5J%Zj^k(TFM=l?2_894A0m_9DxGX?N2u3!Y zlcKewl)n^Z3DO)pFjTL#KO&~3Hm=6p1G4DJh$G&>(3Kq$>|ji4V8%6zLj&1L2L>k? zGko@4@RrKuDcPN`&CfL?_kceV7%rj%*_)0JXs+Z!G^G@id? z=_#Xe*9BK^iPgdev0V(vZdxreEpUX1N)xPD5%;*m*XT1MZ%iMR!w4IqVkIf{@y_yj zuFf~U1=A3}y)JNv6Nk{j-hk3Yhz+OEo*G@B(GR2C*0E3xZQ8FC52O_0!MgrqEEu5t zc3U^6lOy{#JcT*pAKYd7f+5s&!Yq?%@LlV}G??X;&Yroy8wcT&Xq}CN16gS_GY^za z8OdDbXa7-Y7>#s`Iv{kIJN($AZERSVR>7y`h>#eJ%Wo`rHs%Qvc$)#p{Ra79@umpI2 z{p+S+)kX9xoEo-S3nG+E+L^Yi)~&c#D3gfch~)ev$$#rdRjEWWPoziL^8&gq4>aKW z6@=W_Gv}<D8RL8<-mAXrgYKw6ZQkp1d(Sq>5z{(( zfM|1WXEJe>ZQ+iqlCFgCmb%6R=)7h~lrJJSo*!}ozl`>(?B8M_wuhb^2P?XN$HiAW zDVR41$^U;`YZvf|#gw*fgAa90*70Z%b45;LxD-j;z}Q5UTFy7FzpcE1MOnLh{p}2> z`kV8rMiQR+rzTpal^WvXnDQ)!AXjYmkd9+L?yTY?o9|O$#T?gqiS}}a!y7eRE+x)G z+5@E__I|J^Sv|HD+Ox^;=zK(Z)#up_{8*@bb;R<7QzEOXiW07R>|&E@MP6d)0z+iA=Man)kb z9UJ|X^*!r1XUR=|nKm=iGOM8gpjz4j*yxDg+Ir~5SxIx3AD`L6`w9oO*3h_cFtINb zo9Ih0bT7-zxmf?e7(L!)R(>AE(eB9vkXrW(wAwgU(0GH+r*b~|^0I|6ob`H^SZ(j4 z=ijucgtGI^XImI*)uc6Qe!!(X6VR%m91;kc4Sszjk4EVW|ia&#{P{)(ZO;l65143N&&`pGJu{zwv zDL8W9iCj%%f$S3$R`lQ^x0_A6k)S@ru|xq6X6*9E_bglD$8JtW?0d)k$cW&V@=WNK z3Ul!qhIJ4yHeZQSc<-(>wN0=IDi79nChQ1}kCO`Gg;Cl4LTt+AX(r%Wd7swvGPZgP*>pqNOp zMY^~~f%0UGfZvR+)*a)cQvN^)a|c_q0N?!@m~rT%6j5)Q=4TAsFfX98uHWNGk^Q5b zKpj&iphdA7b?Gu+)fn=zaNGq;&@}u&{jOE?@2Bm$iFfmpHbf*s+N>Qo6c0M$v4DBp zrJ{9H`mIFBmgCT8T?kUxUTM$irE{LK&}92Ys^!w+pDhdZi;L+dXrb@q3OIesAvq8s zX542Zd;=Tx#!uEmW@kSwDo=n>>z&}B1hdFtVDYBQy7VXEzeuXE@w0@NP(K@G<-s^nF9q4fG1hLpo?;dX6ckPR@2ug06nRq98Tx2ilOYca|SJ;;~b+XmUnv#qPe%p)4KOpgR->CGpm6KT^jhRB^6 z_5L}tPdnrIn1k~5x2-=;>}{m}M*Ez+@t5{_@3*f$UcXvpcTq|v9| zdhkj5x%Qgl)d(Z`!kX`&s?W+~_wVTQ!SvLp>GNqn*?*=!f8O#37RkT=#1$R-`N;f# zT&m9-_FQqHKC|0@*{#nxAAZ2t^(PKoaD{$8?fRv=^m*i}lPB~!zjpfd_VfBTzh0jo z-Ti;xtYgd&-l})#pdQ)PAKtKmL=ywk|Jo?)bA$ z>*r_w_+MrVU}xTO-=+F&t$yPUeP)lmv{it6VfOwP>GPSddlB80lH@6U+uo?pn{WH& zdI9j)vu~O!01iLx;ky6ukdZRYc*uRP^XujKPpfBdCy z?csCR`+inA$mc&k^Ha4Is4qQX;L-E>eDanXR}d{t9(ejmC%=x*TgJ{k@FhMEre_>_3V^FE{o!bh&owW( z{sSN7v+ZAB{Huik@IB9&^Ncs}dGD!rTv-8tXMX1Wr~M6|lb?S0g5L*__rLl-f3_3= zt~%kn^PUO-f0n-U%TEUr?`!>&1zQ2+Xa4G`FMAh&eD4n*`Qw!>;KQ~bUETDABzfYs z7rg5Uc!|F9mp}N?Ag;A-&pG+IPdU;5`yJo`VA0lOP)B`dgp&W z>-gmDO$W9wdGGN_#{*~l>R&HxN;=>Df#KV~)|C9`#IuKf@0BMcE7v}_y?^_xr2Rt| zp7)aHo|xSJ=sgG8pWmFkV8e$_+J1O;vTD_*Pq^@PWzJu0 zLzQICNB+6mf8kll_}^~&r`NpgoaEu>^?&9a-{?#>+JZ#=Uv8UE!Zzqq8onhd|DdGGo?BgswgICA|rCjT(G`}{xNf5tz* zD!FCwjkOhz-j=kzW#bQi`nw-ZmOuZ^H@@qhuO%Cno_Ff@Km2)e@8=%e@`Qglo%@^q z@?CG;by0f%&9A;~)=3A_&I7Of=EvXs&h*~7AN!TkiXp<;&jmv))fUGnw;& z@7{CY8B3C7d*8X`-Fu&v^j`G6rN8&tu4Lg4<{z23>UqiXFW;5)^j(%TU%UUkAAfLX z^121bzy7M8Ym%2e?b7Eg|NHBbJMVebt^IF*ZPK>+p*OAG_>Sc6ho0NH`jk&3)h~=+ z@Yj>yiI)-Haj)rN!=bJ{cR?d-3OnpekF~@ zRcn1?PugrB)Wb77JEhurdTlXWAsC{JO|&hu;lm7C9?#iC z9asn+ZB>)meKi)TuY*=(nRX#qZjiEr^K(LhR)ShE6xApvOZHTrURifg=?GHI49-%> zb#G2yEgyg3;Vjg}aPEwUmNPUUtK9;1{WgY~nDp`p88IA>dvCxXY^8Q3tN4XnS<+>; zfg2s@_HyXTe)eN>TZ(O0w`#vugU+R622@vnbNed>iFek{kp1MS=KarEq)>!&^S@ zsm#dy2Gs<<;9O=iHmWzQbN$lcz_yQfG zcdk-B7dvpce(o+ciapnpqsD6;B0^OWTHkUROqML+%{74#5VQn*z?LQLhR_x@t0$MF z+)qa+K`MD)Ty=fL3hXxu0f*{4?5aAd0YfA}%ZseQ9ic$n0L^-YLw4GCRw|8DU7~_% z6b7$5#u7-|Zd)Xvp1+JWm}LM|asWH)$y=Z|y1|~5vJNfIQX}w?$dh0fR_(;~9CoDd zryMU3%OgNbvf6L9z9pTVk9mQd@#9^fwLim02BO1U^5rhhmMmMIMUZN5IjD@jpe&Ns zsgk{NaeHM+du3^RCCjqy`SFU;8>e&@TIp1>+F~b4UH<%tPF5y3M_uXEflPT}N%3s) zAam8m2ePCS)Y~*fdVyujvC1CjJ!IyoC#-~lMW0LrD_t*K`VM`nkLSKT2}12Xg!8_q z*qxW`IfBZ>IPqs*uA3cf#H#Gn6)X1IywutTe}cG*#Fz_7%P@k_Ai=%7hQY7)azt^( zNc>e1PggE4jNQnMGfqoOTNAin&X#KEAm!_!6J!1WE0x1Q<)}h=Lu-Lm(IyIU! z#J|hsdWe&2;3*X#Wq3qFEW@R=t;YqIl_ZN8P~)K1^Bm;K0dInNU}c?okPyC_t5X&-pEhQMTM)*! z2=#`;X|tGMnKdWX*6i&a3UdjE;bS)$7F!0SLjLv>msu()ihOLl|4(@v=8akpRm2Q( z_N6u2n9;l|awuHQJStw;%PSUV82Vdf@uEafw=L&a7{ab7!QG+ZF+qj^j_`r^^c%w? zPeglG=UI*=DCg;|a~7@c#h+%osc*J^{bD!C60XuMwaBD+w)|sDr^|o;WtzNw&Pm7H zYsdHtZ~JfUPs+&gf1gCbjzRv{te@e3eLL;XXP$C9K7M~l4~8qGLQ)5oXwlg)XY?WW z15CsTg&DHE7jFkV&p|;_&^wV33%IVye=U2qGzxI0$uXWvb16Va{MYZ66DKv}v)F5r zqH>w()O}j;PUXQEX>KzSgZJopCsI`glU!4lnA^wcn+?ZI!4=jo)G;w_6lMGH0g2j% zeu+G7Z;AI9lV@#~8P1fVkzZDpxT|bM4jC&@iR%-~kw_5oRrAhccoc26YPN07r0#J8 zYN)?lXg)C6t?WV^=O`8ZSfj#QhYvJwRO6J^2qWRb1~BnH#+4=o!*dD&Lq7^$>H zi4r{=1pO%(6Cl0KDd;j?V4)@rys7FOLN8LQW>dg|{1o&cWPd{9SV}iO23+89(IHyM zN!fx2$tP|!Z2l9mzOAJWf%CkVyfB3Da%34Vl98GwOr@o ztC&KG4@h)N;h`GBRKNe$N*{5Jy2$^~Xmu%m@Gv?BNPy7hNSmJuFUkr&q=4qKWjdlB zwPF@orj0+g^mTcDfnCP4W@y}Q;4t&zTPJ=@DxF#6a@$+?#hu{^Lt!Jo{1nu!H1l1$t{_}2fVng!8dq+y?dWj&?^NYAo#4kNe{A?t!T@?c)?6f!lqO66_QdohG^zRQobx&zyfSr0 z#?ywMn!z*cfDOMrLh;qZvUE5>Cw`%gT&Dgey}|W^KW;qDKn77-8c1+Wz%n*llaZBe zi=J(Tpd`{zDC5Xa(!x&EBK%~EDZu%>_R7*>LXJZ`2=@_KkO-3h2$@-u=SEh$Cs-Io zsWP#4Rn~9m+O!T`F23K@+Y`b`1s@87nPbdc?@_ph^c!l`ET`N&qku_7W&~%QABVW4 z7(&iEH;;6y-omm?NlXw5tV5^|Q0Rkhi+{lBSb6-8eq6aO!iUhsmWHJPOF={1fFrny zrBB_Br-$|{jWtU@Cf4=9ob!zkVz0V2fZYH20 z)^2p6n1j^)DU4<+wc-))Fi!&jVerU<;5-6i@96N={e$>Oa>Bt>u)__7Xjar{Oo7{& zcqg|wgk1(Nlvb-kSPsa+rXXjs1m?9yZXv(Tv(PKGZj-(~D!GQPDESkH6Q!{w&Ge%j zxFp5IM8JcHo{Sl`So;&Qmne~3+}YW||DB4iDexlyB+_OVM+nSVnkFpPQS5jH9B-Gd zC6))qokgJ9;?AqHmA(1YmP^US)pIPX#Txx|h5Si2kY8!+7oiykXqWf+nHgA z%;GSzDPW}O?|Ge-Rs7YNt!-p?Je?-L0#PeBr-qL3B;8IDk+Ok%I)<*}2?cZ|3Iib% zL(}REG@Oh4U`1l@D2|(b(xL1!nVq%IR8(Su^J7e1n5$dy&A^z*%+8y<3STuw{$`hh zbm(jZSdL3&r-x>@Z?y!^FtZhn)zlt7T64nQkw%?PPIX2Q^ajY@!$^7 zeTVi*SK9*5yM=Z^nVLNpX9fClm)zKrvPS)|@p3w-7csz@3}M~PTEeza-X7$$Cmb6& z-c>qtw~x@!2k~+Xj?#St9@tT2$+dx<6mTvmrZinRH=q~rLx-7D$uTiKD3V*r)Er+U z$r1e+^KWx}@_flKZSH}L98YlpGETK@=_p)4A8LUlM6Ryb9gd1ByHhS+FQINLBqio= zCD8H3$Pv&Hat~1B6m`3vIAsY%JkaTEEpXisDH5}*Qv`$|io`PR+;0J0m-U8Rn6P#M z$x(!Ar7e76ojpQ1ytcSoAwyH(YX=!&QD*iOc_88e@;ssLN*{6zo){xT7~&Vfxh4_W z{qz-&B4EZOV}0ft!YXubR{MLMqef|b4`F4`6u1x~S<+&GYt(U~c45SqEsSCqC*Min z`2720uL?L!!B-rYIxNTAgreW-4Fiw3X>c%jTBSiv5yvjUHyDpd#Zf6 zVd0YvlspI^V7+xpJ@Zf|`NBlnx8or7DYd#uRm7bD87Q zS~~DDmbigubOfqKi86U02$(masHU+BV1N|&ZExKr;<1r1DuWJ5BwHedq7RN!^kvLK zdOupZpH?pw#h@|efS~OBTgi8@iYx6Z$kpQ5Or9zMOkH*CHRA|cp*-r7Vby+K`9bMx z+m@_ns`BO3AC%!y$mr-S)1$CAAq&vhy~o70eiUXU12t$A&egalKO*s~T=_xw z*`(ISYMmm(LS}=h6|o>h!ArP&fE3e5~ND4hqq;SJAjCTqeAuf4rJ4FJLi++1_Mp4;Z}5Am#@~ z7Y1u`XgQ<({~bQn8^C|jXqNuBNFY%JPE6*@f}t9fo5J>tMHKk(SdHs3yr) z=eP+`c+#@-iWto!qQII*mGzfGA@;%Vhm&_tH5H1wgn8d8qRH{c)L<)LUixyTZy6j;C~9TtE~q^CYtA%&~R zI+Ny644+j4Ds90ZuH0g@gEBg;6FLn&c69Qs#nT4_ad2lu;Y?Za3&kWOUgh+gFR%zR z1=fs8o;Up}vq$x)_OXQxaliq*tm-Uc4E*3exHhK=X9h3v;q61shLs~|fIDcwUz~wq zE>ih`oL(GTSgWV*%B{gm#`N1PaTlbB=aOgkExKP~eZYiG+!ii;SQiB0GD7=^#~sD& zk$l;*dvbv`ozb9hF=UhxJo%gL$HLH<9qtxIf(f$f|qV6PctL6 z{}ENdl54|R7&w(wP8<`Q;8s;JBu~XxyFNP_cq7IEXke+~^23L|c^bi=f(*wV${)L)@`YGxNZviO5yOZpqM3X2``yps=b5| zY&3V`8n}Nup9kPFN7@WyBW5LiAWIep<{I|{tk6t6^eY~Y>-EZN zqq0%m-maB2ntRx#VCV+Eh+2KH$HE^A)v;|>(Pk=^DkbTrxy##oJS~^ICCAe8 z56#ONsQA)DWR){yBs9jzOm)EOvATg^H|(sT284uPMlRKeuul`7cH_oY{Ka6M)n-7J z<>;6R$Wo{aItu}SJZNU8B`vodNo7Ks!3D?Af0To(j*jdI9yq+-BIWg}d9a%R_OdXd zvzCk-yvjmItOUc!@GJA2q6rkMtAe|2q_Nj)R9k4sDsFEQjO?}}WQ?+fE$d9kWT^#J zD}>-KB)iMYUiB!ETw9~NCe63Fmph2zP)786Qya74%VjX3@(E1YWzk|R7cS9m3_4mZ zf1RF-u_*nJ6Hb722zmo2^AxEc-nKOp-jQLebCGV(^`NM7E*T-P*!X?UW?V%FDweuy z*s3+VS`=t2XPSIdj^5-s9@`;-{AMCM?@0500H}%DJN@b_3Tu;635@jv}1qWwn7SYLc!?^h!3}&?yJH!9|sMN*FiwAjmhV zB4%qz9)xCGcjt1#qD=VSI1Uz0fMvIm&Rk`X@_=g=mYAiBaV#XyYFWE?U#h>F`_`hU zsPujlz{fpo62ZFAA5O0T{^U>g0a~duR-jL6tsVpRgLJO1%SkR-i*-oJnH&e=zuDkS zqneM&{TpqE(yaB)V?UOh+SQ{S`Fs3gjgz`3B(v9^F`fEuRX0_EMuW|{j zRs*mFKL$5B;D)T7$M>!{NQYCrn)`QXg!ams_R5B~O^fnAr$_K-R`c(xl=b;i^ed~R zw28%CbpWt4@;*t(1BEiU7VoYp-kC1P1JbfMR|=$k8=x)luYe;FoWbnN8c543#_}0G zBp2UtO3&X)&(oo=Xt`8}&R_FMZ^iIj8}3ppANkG4;flG&)qwn{qf+JUsTAkYQIz)* z+ReKLCK_o(3j=wUz;Fuxi}$j=R9d8~$!ufgp>KZFTV0rGa7k%mOpssX?n!D+W9LbTXi<6-=Ky%u~_cuzDASDOJ4bP9H_LSzMyT`WTFb;|=MsVMk5BVg)sYfNrWGl33`n-0us-LJ*Rm)#gHJHEo zokYY9$4KO8GxFCP7|Qfn*gqE&_Jk>%)lRW?Z~zQo7X=$uBa`CynfpT;y8 z)hQG0GDcY~hUA_^o%uKydm7@ah8+l5M>1`xc95G}pVgdxh@`mM+zVSbj3J8b@6N0A z@6E`!ZfDwCNW$h_^YMN*9Rh0eJ(?AWDQ5a@*s`ft>F|Dwk+{7mR&Qp>-L@UQ09LCO zgLa-HA+aW84NF4+|B1{YJsOXHRUi}j6ZoIdWsoO<>K=2ja`-4)+-PCTy=*Z#$|HVM z77}s;((bttZA5lx=?R!UqX#7I&=6;Wi0#AeAvHo$hDN9&2748G6jm!0pkQ>U88e>? zO^-YiN}g;fe8wDP)fDLCj3uzBI!Iq_+tyy$A%n&7qj>Zv)7Zv6j9uMU&0eU*bXnx1 zHrR+L(?MkF%VP!+9l|*8FhvtHRoI(Irfq}&F0&jU73pO0@8KI{o)e&LcJ(4`Gt;@f;3?vUv zoLjR8T{g*@Rg#jh7=S(`1tU?qf8(Sk2H|;o9uK(+PSDwUh2^X%3q2hS6`%WKursBo>gCQi4H`k;a&PwXLVU0)l0m8YXSn(oyGJ z_ju>*ZLh3j&Qx=9PH?G0r$wB5dHo>#$l6*4+;dZ`VF%eBA0TMPAcBUnK0BG|E$fbj zZpp^dVBJVdHfo-g&!&@LWAh!At?nbT!w^2@x>kNVVQq>{=O?&Au|oy=Q9$u8bhx_; zE~+6K1CEibIJzlk1BVTf!40KVt{uX)G{DguOP1Yh*)Gw@$`JMbuIkMx{BiRL$jwv_ z*02FFjA?_rRusLJeb9?dSALOedz;fwp(MY2?n>KHaEYo1b2{}Gt-W0c*WDxXZ#<83 zB3$hx!l%hAIJcI!5&AQ+es!}9 zM)_TRTx7wuEWW;&#v41FbGes2l+UJpg-BX-#l?lFL$`Kf2TV?HoLAwJ*24~_@Hgud&`S>n@SGgIqkR)wB;keh&y_q9d zeV4|~o>X z84Zn?4zJ4YsLPj0-^DA)YLBwF{&5VlLT-WEc-UpVDIXo`iwqxLqk2Sh3x@Th!N7p> z{9p?Q!KjB|pem(WxRo}~TWO$gie2l?$K!KBDcHnKGI5VQ>y=63>pAE1tVuKmVsf@4S?I{;wX4El-l!&SCo4{3kOKd2fjT5_w=9Ao-;UWh~ zRNXoG0)_KNYCDR4Q8Xp1DwGx~L9+zo-lTzwOPqis-aB_M$_X=@ zzur1fr%6u}(bp$61}Ob3IaHi%YEzBGXY){2E`6OpMn9|9$F#$1G_k*J{w|4oH%>(j z#PtrCQ1vGaja>1I#cW`8?i+8PO?(o3Q1q8=P|9bA3(B}LZa8lHYmA@;eb45|9#!&_ z1ipDrK_=Oz5fwue6Ox=E>SPX#Zri=2uh28wzT(p1?ScSo`HTi zl`F7mFs}!%^M$+{?y(_dxmQYs+fuhd*R7)Kz2*|1>4MSRiZW0Ly2ahNv1?R@`_Zr? zbXW`AQE7daP(^04w@Vjyi1ulu2Fn^SFo#A{77vnr!kJEGg zkr>W#71Icp*DPw_cagZp5UAe=40B~kzpv6C(Fl>n+52Jo#>GeR7%L16f{?26k=21S zl>FSu7VSJUkG8V^In21?2%8Z7+QTm zMW;4!6pY7^mZfrDp1^*g_^}$?u0W_ zb_juUg##$8ZK@8|H_flqD@Z&kAm?^2sc~PRmFJnPusF z=3YeEkEggn=iy(i%fr9SpG-1R>_2rw=~|io@GwjTF%A)jc2Vu&U72Y~avp+f9`dqe zP>d08x2U+F#c4*m2?Lsz%kLo*PE{!b3F>5@j@<%VWuj~d0XJ+8sZNn+P8j}b&~A2( zPK<%p^4SmVo=|#8`BhU>!&Pq&|Gv5NQMDHr>^Vnj#Am2K40ssW zTOMxb$_pYL(7Btx2x1I7)t@Y{kW#y|W06^L^=!fA{%dI#tsk0;DINGyc--&K$i+a| z37wDpec8c=ci8q8t9Sk1vcM`+_uCWnykL>Xo!iOdX(75UCvJd#CmpXOU;LQ!jKr-z zk7BEKW;hKU(qRE%fv#OenqtUKHCYZe7e7`@VO-j}StL^kzH-;O>RGi=Ph>aaG7Fc~5nj#;y?<_@n z8#bosXtliHYUSLxHJ_wPWqjZ0IK1rXLO7qlzFZ2y7MamT{;jR12W>2|FI{k^hgw^e zOOvFZn!90P_t@xxL6Qwd(&C@3^VSnj4tnWlK_5CWiW5lQ%dH%a!|w9ovZ306E&cS7 z_NagJrGe-*f)ld22KMMak--9dj5lr*ETleX%pE1db<}as8q(V1C5A3+W-+*Bv8fmVLZD3sejm-t%Za; z7#Vtb|M))R=;Y9>3u2!NgP_}2#Y>eVDsRxnAO&du65Sh3I2tJ_>7Xx(EEn-V6uLR<>F z3hiVc+T}!AVf&Gp?i_}VEU7Fx?|e&$7FnV#4k5YP@pgUtbPRzutY3TLnkCeZ;M9Z}j9A?-{;RwV z@G28QE~H9Ns~|;jUyU8d6F2bz%q;`1hP$o#HSJx{GL_A{dTYGcE$%=*vWMp4BjZjy z;s3^lWd=S$<%-sZ){iwH^<;FwmW_h#<7KPEco&9_d ze}=>mP9Sat9Xj`wnsa7v)>|HavX9CE%BaE=-vbkyVINvOnxB%7rc7mlAuO8#sF!)5 z9Tcv7IzWSHQ3vp%?a(-5867LII5PfP9arfcJqS_2@W=!ZcUPyKtS>Bb?GU#^u$f8d zWdiB(eHN(HkhR5m#}f=lQ9ejU_L9`$A!I3Q7352IIfU!(WW(epkL-BkQ0DV83Prb?Cib)4Rfjp*T6Bu4M7kEaV02We1QjKq=()W+3Idrk~Zb7 z*OQ;K=_apxjQVt53x(>0j;?MOxLM>sPA8hv@8f{_)!c!!rKz%^TV3<8W1df%3pL$lyk!m;X@OIZ=Uc_rmL+b6HR19ZP%aM+j{|Cd|@2mg- diff --git a/artifacts/polkadot.scale b/artifacts/metadata.scale similarity index 100% rename from artifacts/polkadot.scale rename to artifacts/metadata.scale diff --git a/artifacts/westend.scale b/artifacts/westend.scale deleted file mode 100644 index bb375bd9dd6e7a274a503e75699b216c54bd75cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 283842 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 = OnceCell::new(); -#[cfg(feature = "westend")] pub mod westend { use super::*; @@ -47,11 +48,8 @@ pub mod westend { active_voters: u32, desired_targets: u32, ) -> Weight { - use _feps::NposSolution; - use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; - // Mock a RawSolution to get the correct weight without having to do the heavy work. - let raw = RawSolution { + let raw_solution = RawSolution { solution: NposSolution16 { votes1: mock_votes( active_voters, @@ -62,62 +60,14 @@ pub mod westend { ..Default::default() }; - assert_eq!(raw.solution.voter_count(), active_voters as usize); - assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); + assert_eq!(raw_solution.solution.voter_count(), active_voters as usize); + assert_eq!(raw_solution.solution.unique_targets().len(), desired_targets as usize); - let tx = runtime::tx() - .election_provider_multi_phase() - .submit_unsigned(raw, SolutionOrSnapshotSize { voters, targets }); - - get_weight(tx) + get_weight(raw_solution, SolutionOrSnapshotSize { voters, targets }) } } - - #[subxt::subxt( - runtime_metadata_path = "artifacts/westend.scale", - derive_for_all_types = "Clone, Debug, Eq, PartialEq", - derive_for_type( - type = "pallet_election_provider_multi_phase::RoundSnapshot", - derive = "Default" - ) - )] - pub mod runtime { - #[subxt(substitute_type = "westend_runtime::NposCompactSolution16")] - use crate::chain::westend::NposSolution16; - - #[subxt(substitute_type = "sp_arithmetic::per_things::PerU16")] - use ::sp_runtime::PerU16; - - #[subxt(substitute_type = "pallet_election_provider_multi_phase::RawSolution")] - use ::pallet_election_provider_multi_phase::RawSolution; - - #[subxt(substitute_type = "sp_npos_elections::ElectionScore")] - use ::sp_npos_elections::ElectionScore; - - #[subxt(substitute_type = "pallet_election_provider_multi_phase::Phase")] - use ::pallet_election_provider_multi_phase::Phase; - - #[subxt( - substitute_type = "pallet_election_provider_multi_phase::SolutionOrSnapshotSize" - )] - use ::pallet_election_provider_multi_phase::SolutionOrSnapshotSize; - } - - pub use runtime::runtime_types; - - pub mod epm { - use super::*; - pub type BoundedVoters = - Vec<(AccountId, VoteWeight, BoundedVec)>; - pub type Snapshot = (BoundedVoters, Vec, u32); - pub use super::{ - runtime::election_provider_multi_phase::*, - runtime_types::pallet_election_provider_multi_phase::*, - }; - } } -#[cfg(feature = "polkadot")] pub mod polkadot { use super::*; @@ -148,9 +98,6 @@ pub mod polkadot { active_voters: u32, desired_targets: u32, ) -> Weight { - use _feps::NposSolution; - use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; - // Mock a RawSolution to get the correct weight without having to do the heavy work. let raw = RawSolution { solution: NposSolution16 { @@ -166,59 +113,11 @@ pub mod polkadot { assert_eq!(raw.solution.voter_count(), active_voters as usize); assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); - let tx = runtime::tx() - .election_provider_multi_phase() - .submit_unsigned(raw, SolutionOrSnapshotSize { voters, targets }); - - get_weight(tx) + get_weight(raw, SolutionOrSnapshotSize { voters, targets }) } } - - #[subxt::subxt( - runtime_metadata_path = "artifacts/polkadot.scale", - derive_for_all_types = "Clone, Debug, Eq, PartialEq", - derive_for_type( - type = "pallet_election_provider_multi_phase::RoundSnapshot", - derive = "Default" - ) - )] - pub mod runtime { - #[subxt(substitute_type = "polkadot_runtime::NposCompactSolution16")] - use crate::chain::polkadot::NposSolution16; - - #[subxt(substitute_type = "sp_arithmetic::per_things::PerU16")] - use ::sp_runtime::PerU16; - - #[subxt(substitute_type = "pallet_election_provider_multi_phase::RawSolution")] - use ::pallet_election_provider_multi_phase::RawSolution; - - #[subxt(substitute_type = "sp_npos_elections::ElectionScore")] - use ::sp_npos_elections::ElectionScore; - - #[subxt(substitute_type = "pallet_election_provider_multi_phase::Phase")] - use ::pallet_election_provider_multi_phase::Phase; - - #[subxt( - substitute_type = "pallet_election_provider_multi_phase::SolutionOrSnapshotSize" - )] - use ::pallet_election_provider_multi_phase::SolutionOrSnapshotSize; - } - - pub use runtime::runtime_types; - - pub mod epm { - use super::*; - pub type BoundedVoters = - Vec<(AccountId, VoteWeight, BoundedVec)>; - pub type Snapshot = (BoundedVoters, Vec, u32); - pub use super::{ - runtime::election_provider_multi_phase::*, - runtime_types::pallet_election_provider_multi_phase::*, - }; - } } -#[cfg(feature = "kusama")] pub mod kusama { use super::*; @@ -249,9 +148,6 @@ pub mod kusama { active_voters: u32, desired_targets: u32, ) -> Weight { - use _feps::NposSolution; - use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; - // Mock a RawSolution to get the correct weight without having to do the heavy work. let raw = RawSolution { solution: NposSolution24 { @@ -267,62 +163,20 @@ pub mod kusama { assert_eq!(raw.solution.voter_count(), active_voters as usize); assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); - let tx = runtime::tx() - .election_provider_multi_phase() - .submit_unsigned(raw, SolutionOrSnapshotSize { voters, targets }); - - get_weight(tx) + get_weight(raw, SolutionOrSnapshotSize { voters, targets }) } } - - #[subxt::subxt( - runtime_metadata_path = "artifacts/kusama.scale", - derive_for_all_types = "Clone, Debug, Eq, PartialEq", - derive_for_type( - type = "pallet_election_provider_multi_phase::RoundSnapshot", - derive = "Default" - ) - )] - pub mod runtime { - #[subxt(substitute_type = "kusama_runtime::NposCompactSolution24")] - use crate::chain::kusama::NposSolution24; - - #[subxt(substitute_type = "pallet_election_provider_multi_phase::RawSolution")] - use ::pallet_election_provider_multi_phase::RawSolution; - - #[subxt(substitute_type = "sp_arithmetic::per_things::PerU16")] - use ::sp_runtime::PerU16; - - #[subxt(substitute_type = "sp_npos_elections::ElectionScore")] - use ::sp_npos_elections::ElectionScore; - - #[subxt(substitute_type = "pallet_election_provider_multi_phase::Phase")] - use ::pallet_election_provider_multi_phase::Phase; - - #[subxt( - substitute_type = "pallet_election_provider_multi_phase::SolutionOrSnapshotSize" - )] - use ::pallet_election_provider_multi_phase::SolutionOrSnapshotSize; - } - - pub use runtime::runtime_types; - - pub mod epm { - use super::*; - pub type BoundedVoters = - Vec<(AccountId, VoteWeight, BoundedVec)>; - pub type Snapshot = (BoundedVoters, Vec, u32); - pub use super::{ - runtime::election_provider_multi_phase::*, - runtime_types::pallet_election_provider_multi_phase::*, - }; - } } /// Helper to fetch the weight from a remote node /// /// Panics: if the RPC call fails or if decoding the response as a `Weight` fails. -fn get_weight(tx: subxt::tx::StaticTxPayload) -> Weight { +fn get_weight( + raw_solution: RawSolution, + witness: SolutionOrSnapshotSize, +) -> Weight { + let tx = unsigned_solution_tx(raw_solution, witness); + futures::executor::block_on(async { let client = SHARED_CLIENT.get().expect("shared client is configured as start; qed"); diff --git a/src/dry_run.rs b/src/dry_run.rs index af752f4e3..bba087b8f 100644 --- a/src/dry_run.rs +++ b/src/dry_run.rs @@ -18,70 +18,63 @@ use pallet_election_provider_multi_phase::RawSolution; -use crate::{error::Error, opt::DryRunConfig, prelude::*, signer::Signer}; +use crate::{ + error::Error, + helpers::{mine_solution, signed_solution_tx}, + opt::DryRunConfig, + prelude::*, + signer::Signer, + static_types, +}; use codec::Encode; -macro_rules! dry_run_cmd_for { - ($runtime:tt) => { - paste::paste! { - pub async fn [](api: SubxtClient, config: DryRunConfig) -> Result<(), Error> { - use crate::chain::[<$runtime>]::runtime as runtime; - - let mut signer = Signer::new(&config.seed_or_path)?; - - let account_info = api.storage().fetch( - &runtime::storage().system().account(signer.account_id()), - None - ) - .await? - .ok_or(Error::AccountDoesNotExists)?; - - log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer, account_info); - - let (solution, score, _size) = - crate::helpers::[](&api, config.at, config.solver).await?; - - let round = api.storage().fetch( - &runtime::storage().election_provider_multi_phase().round(), - config.at - ) - .await? - .expect("The round must exist"); - - let raw_solution = RawSolution { solution, score, round }; - let nonce = api.rpc().system_account_next_index(signer.account_id()).await?; - signer.set_nonce(nonce); - - log::info!( - target: LOG_TARGET, - "solution score {:?} / length {:?}", - score, - raw_solution.encode().len(), - ); - - let tx = runtime::tx().election_provider_multi_phase().submit(raw_solution); - let xt = api.tx().create_signed(&tx, &*signer, ExtrinsicParams::default()).await?; - - let outcome = api - .rpc() - .dry_run(xt.encoded(), config.at) - .await?; - - log::info!(target: LOG_TARGET, "dry-run outcome is {:?}", outcome); - - match outcome { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(Error::Other(format!("{:?}", e))), - Err(e) => Err(Error::Other(format!("{:?}", e))), - } - } - } - }; +pub async fn dry_run_cmd(api: SubxtClient, config: DryRunConfig) -> Result<(), Error> +where + T: MinerConfig + + Send + + Sync + + 'static, + T::Solution: Send, +{ + let mut signer = Signer::new(&config.seed_or_path)?; + + let account_info = api + .storage() + .fetch(&runtime::storage().system().account(signer.account_id()), None) + .await? + .ok_or(Error::AccountDoesNotExists)?; + + log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer, account_info); + + let (solution, score, _size) = mine_solution::(&api, config.at, config.solver).await?; + + let round = api + .storage() + .fetch(&runtime::storage().election_provider_multi_phase().round(), config.at) + .await? + .expect("The round must exist"); + + let raw_solution = RawSolution { solution, score, round }; + let nonce = api.rpc().system_account_next_index(signer.account_id()).await?; + signer.set_nonce(nonce); + + log::info!( + target: LOG_TARGET, + "solution score {:?} / length {:?}", + score, + raw_solution.encode().len(), + ); + + let tx = signed_solution_tx(raw_solution); + let xt = api.tx().create_signed(&tx, &*signer, ExtrinsicParams::default()).await?; + + let outcome = api.rpc().dry_run(xt.encoded(), config.at).await?; + + log::info!(target: LOG_TARGET, "dry-run outcome is {:?}", outcome); + + match outcome { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(Error::Other(format!("{:?}", e))), + Err(e) => Err(Error::Other(format!("{:?}", e))), + } } - -#[cfg(feature = "polkadot")] -dry_run_cmd_for!(polkadot); -#[cfg(feature = "kusama")] -dry_run_cmd_for!(kusama); -#[cfg(feature = "westend")] -dry_run_cmd_for!(westend); diff --git a/src/emergency_solution.rs b/src/emergency_solution.rs index 4e098a1d1..8ad251503 100644 --- a/src/emergency_solution.rs +++ b/src/emergency_solution.rs @@ -16,24 +16,18 @@ //! The emergency-solution command. -use crate::{opt::EmergencySolutionConfig, prelude::*}; +use crate::{opt::EmergencySolutionConfig, prelude::*, static_types}; -macro_rules! emergency_cmd_for { - ($runtime:tt) => { - paste::paste! { - pub async fn []( - _api: SubxtClient, - _config: EmergencySolutionConfig - ) -> Result<(), Error> { - todo!("not possible to implement yet"); - } - } - }; +pub async fn emergency_cmd( + _api: SubxtClient, + _config: EmergencySolutionConfig, +) -> Result<(), Error> +where + T: MinerConfig + + Send + + Sync + + 'static, + T::Solution: Send, +{ + todo!("not possible to implement yet"); } - -#[cfg(feature = "polkadot")] -emergency_cmd_for!(polkadot); -#[cfg(feature = "kusama")] -emergency_cmd_for!(kusama); -#[cfg(feature = "westend")] -emergency_cmd_for!(westend); diff --git a/src/helpers.rs b/src/helpers.rs index 71b93beae..8a73f74c8 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,8 +1,10 @@ -use crate::{chain, opt::Solver, prelude::*, static_types}; -use frame_election_provider_support::{PhragMMS, SequentialPhragmen}; +use crate::{opt::Solver, prelude::*, static_types}; +use codec::Encode; +use frame_election_provider_support::{NposSolution, PhragMMS, SequentialPhragmen}; use frame_support::{weights::Weight, BoundedVec}; -use pallet_election_provider_multi_phase::{SolutionOf, SolutionOrSnapshotSize}; +use pallet_election_provider_multi_phase::{RawSolution, SolutionOf, SolutionOrSnapshotSize}; use pin_project_lite::pin_project; +use runtime::runtime_types::pallet_election_provider_multi_phase::RoundSnapshot; use sp_npos_elections::ElectionScore; use std::{ future::Future, @@ -10,6 +12,12 @@ use std::{ task::{Context, Poll}, time::{Duration, Instant}, }; +use subxt::dynamic::Value; +use subxt::tx::DynamicTxPayload; + +pub type BoundedVoters = + Vec<(AccountId, VoteWeight, BoundedVec)>; +pub type Snapshot = (BoundedVoters, Vec, u32); pin_project! { pub struct Timed @@ -50,80 +58,77 @@ pub trait TimedFuture: Sized + Future { impl TimedFuture for F {} -macro_rules! helpers_for_runtime { - ($runtime:tt) => { - paste::paste! { - /// The monitor command. - pub(crate) async fn []( - api: &SubxtClient, - hash: Option, - solver: Solver - ) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), Error> { - let (voters, targets, desired_targets) = [](&api, hash).await?; - - let blocking_task = tokio::task::spawn_blocking(move || { - match solver { - Solver::SeqPhragmen { iterations } => { - BalanceIterations::set(iterations); - Miner::::mine_solution_with_snapshot::< - SequentialPhragmen, - >(voters, targets, desired_targets) - }, - Solver::PhragMMS { iterations } => { - BalanceIterations::set(iterations); - Miner::::mine_solution_with_snapshot::>( - voters, - targets, - desired_targets, - ) - }, - } - }).await; - - match blocking_task { - Ok(Ok(res)) => Ok(res), - Ok(Err(err)) => Err(Error::Other(format!("{:?}", err))), - Err(err) => Err(Error::Other(format!("{:?}", err))), - } - } - - pub async fn [](api: &SubxtClient, hash: Option) -> Result { - use crate::chain::[<$runtime>]::{epm::RoundSnapshot, runtime}; - use crate::static_types; - - let RoundSnapshot { voters, targets } = api - .storage().fetch(&runtime::storage().election_provider_multi_phase().snapshot(), hash) - .await? - .unwrap_or_default(); - - let desired_targets = api - .storage() - .fetch(&runtime::storage().election_provider_multi_phase().desired_targets(), hash) - .await? - .unwrap_or_default(); - - let voters: Vec<_> = voters - .into_iter() - .map(|(a, b, mut c)| { - let mut bounded_vec: BoundedVec = BoundedVec::default(); - // If this fails just crash the task. - bounded_vec.try_append(&mut c.0).unwrap_or_else(|_| panic!("BoundedVec capacity: {} failed; `MinerConfig::MaxVotesPerVoter` is different from the chain data; this is a bug please file an issue", static_types::MaxVotesPerVoter::get())); - (a, b, bounded_vec) - }) - .collect(); - - Ok((voters, targets, desired_targets)) - } - } - }; +pub async fn mine_solution( + api: &SubxtClient, + hash: Option, + solver: Solver, +) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), Error> +where + T: MinerConfig + + Send + + Sync + + 'static, + T::Solution: Send, +{ + let (voters, targets, desired_targets) = snapshot::(&api, hash).await?; + + let blocking_task = tokio::task::spawn_blocking(move || match solver { + Solver::SeqPhragmen { iterations } => { + BalanceIterations::set(iterations); + Miner::::mine_solution_with_snapshot::< + SequentialPhragmen, + >(voters, targets, desired_targets) + }, + Solver::PhragMMS { iterations } => { + BalanceIterations::set(iterations); + Miner::::mine_solution_with_snapshot::>( + voters, + targets, + desired_targets, + ) + }, + }) + .await; + + match blocking_task { + Ok(Ok(res)) => Ok(res), + Ok(Err(err)) => Err(Error::Other(format!("{:?}", err))), + Err(err) => Err(Error::Other(format!("{:?}", err))), + } } -#[cfg(feature = "polkadot")] -helpers_for_runtime!(polkadot); -#[cfg(feature = "kusama")] -helpers_for_runtime!(kusama); -#[cfg(feature = "westend")] -helpers_for_runtime!(westend); +pub async fn snapshot( + api: &SubxtClient, + hash: Option, +) -> Result +where + T: MinerConfig + Send + Sync + 'static, + T::Solution: Send, +{ + let RoundSnapshot { voters, targets } = api + .storage() + .fetch(&runtime::storage().election_provider_multi_phase().snapshot(), hash) + .await? + .unwrap_or_default(); + + let desired_targets = api + .storage() + .fetch(&runtime::storage().election_provider_multi_phase().desired_targets(), hash) + .await? + .unwrap_or_default(); + + let voters: Vec<_> = voters + .into_iter() + .map(|(a, b, mut c)| { + let mut bounded_vec: BoundedVec = BoundedVec::default(); + // If this fails just crash the task. + bounded_vec.try_append(&mut c.0).unwrap_or_else(|_| panic!("BoundedVec capacity: {} failed; `MinerConfig::MaxVotesPerVoter` is different from the chain data; this is a bug please file an issue", static_types::MaxVotesPerVoter::get())); + (a, b, bounded_vec) + }) + .collect(); + + Ok((voters, targets, desired_targets)) +} #[derive(Copy, Clone, Debug)] struct EpmConstant { @@ -199,3 +204,31 @@ fn read_constant<'a, T: serde::Deserialize<'a>>( Error::InvalidMetadata(format!("Decoding `{}` failed {}", std::any::type_name::(), e)) }) } + +pub fn signed_solution_tx( + solution: RawSolution, +) -> DynamicTxPayload<'static> { + let encoded_solution = solution.encode(); + + subxt::dynamic::tx( + "ElectionProviderMultiPhase", + "submit", + vec![Value::from_bytes(&encoded_solution)], + ) +} + +pub fn unsigned_solution_tx( + solution: RawSolution, + witness: SolutionOrSnapshotSize, +) -> DynamicTxPayload<'static> { + let encoded_solution = solution.encode(); + let encoded_witness = witness.encode(); + + let x = Value::from(encoded_solution); + + subxt::dynamic::tx( + "ElectionProviderMultiPhase", + "submit_unsigned", + vec![Value::from_bytes(&encoded_solution), Value::from_bytes(&encoded_witness)], + ) +} diff --git a/src/main.rs b/src/main.rs index eeb5132d3..7b618b9bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,29 +80,29 @@ async fn main() -> Result<(), Error> { let _prometheus_handle = prometheus::run(prometheus_port.unwrap_or(DEFAULT_PROMETHEUS_PORT)) .map_err(|e| log::warn!("Failed to start prometheus endpoint: {}", e)); log::info!(target: LOG_TARGET, "Connected to chain: {}", chain); + helpers::read_metadata_constants(&api).await?; chain::SHARED_CLIENT.set(api.clone()).expect("shared client only set once; qed"); - let outcome = any_runtime!(chain, { - helpers::read_metadata_constants(&api).await?; - - let (tx_upgrade, rx_upgrade) = oneshot::channel::(); - - // Start a new tokio task to perform the runtime updates in the background. - // if this fails then the miner will be stopped and has to be re-started. - tokio::spawn(runtime_upgrade_task(api.clone(), tx_upgrade)); + // Start a new tokio task to perform the runtime updates in the background. + // if this fails then the miner will be stopped and has to be re-started. + let (tx_upgrade, rx_upgrade) = oneshot::channel::(); + tokio::spawn(runtime_upgrade_task(api.clone(), tx_upgrade)); + let res = any_runtime!(chain, { let fut = match command { - Command::Monitor(cfg) => monitor_cmd(api, cfg).boxed(), - Command::DryRun(cfg) => dry_run_cmd(api, cfg).boxed(), - Command::EmergencySolution(cfg) => emergency_cmd(api, cfg).boxed(), + Command::Monitor(cfg) => monitor::monitor_cmd::(api, cfg).boxed(), + Command::DryRun(cfg) => dry_run::dry_run_cmd::(api, cfg).boxed(), + Command::EmergencySolution(cfg) => { + emergency_solution::emergency_cmd::(api, cfg).boxed() + }, }; run_command(fut, rx_upgrade).await }); - log::info!(target: LOG_TARGET, "round of execution finished. outcome = {:?}", outcome); - outcome + log::info!(target: LOG_TARGET, "round of execution finished. outcome = {:?}", res); + res } #[cfg(target_family = "unix")] @@ -158,7 +158,7 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(u) => u, Err(e) => { let _ = tx.send(e.into()); - return + return; }, }; @@ -172,10 +172,10 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(u) => u, Err(e) => { let _ = tx.send(e.into()); - return + return; }, }; - continue + continue; }, }; @@ -184,7 +184,7 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(()) => { if let Err(e) = helpers::read_metadata_constants(&api).await { let _ = tx.send(e.into()); - return + return; } prometheus::on_runtime_upgrade(); log::info!(target: LOG_TARGET, "upgrade to version: {} successful", version); diff --git a/src/monitor.rs b/src/monitor.rs index d1b60eb16..86958bc5f 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -5,398 +5,404 @@ use crate::{ prelude::*, prometheus, signer::Signer, + static_types, }; use codec::Encode; use frame_election_provider_support::NposSolution; use pallet_election_provider_multi_phase::{RawSolution, SolutionOf}; use sp_runtime::Perbill; use std::sync::Arc; -use subxt::{rpc::Subscription, tx::TxStatus}; +use subxt::{dynamic::Value, rpc::Subscription, tx::TxStatus}; use tokio::sync::Mutex; -macro_rules! monitor_cmd_for { - ($runtime:tt) => { - paste::paste! { - /// The monitor command. - pub async fn [] (api: SubxtClient, config: MonitorConfig) -> Result<(), Error> { - use crate::chain::[<$runtime>]::{self as chain, runtime}; +pub async fn monitor_cmd(api: SubxtClient, config: MonitorConfig) -> Result<(), Error> +where + T: MinerConfig + + Send + + Sync + + 'static, + T::Solution: Send, +{ + let signer = Signer::new(&config.seed_or_path)?; + + let account_info = { + let addr = runtime::storage().system().account(signer.account_id()); + api.storage().fetch(&addr, None).await?.ok_or(Error::AccountDoesNotExists)? + }; + + log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer, account_info); + + let mut subscription = heads_subscription(&api, config.listen).await?; + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); + let submit_lock = Arc::new(Mutex::new(())); + + loop { + let at = tokio::select! { + maybe_rp = subscription.next() => { + match maybe_rp { + Some(Ok(r)) => r, + Some(Err(e)) => { + log::error!(target: LOG_TARGET, "subscription failed to decode Header {:?}, this is bug please file an issue", e); + return Err(e.into()); + } + // The subscription was dropped, should only happen if: + // - the connection was closed. + // - the subscription could not keep up with the server. + None => { + log::warn!(target: LOG_TARGET, "subscription to `{:?}` terminated. Retrying..", config.listen); + subscription = heads_subscription(&api, config.listen).await?; + continue + } + } + }, + maybe_err = rx.recv() => { + match maybe_err { + Some(err) => return Err(err), + None => unreachable!("at least one sender kept in the main loop should always return Some; qed"), + } + } + }; + + // Spawn task and non-recoverable errors are sent back to the main task + // such as if the connection has been closed. + tokio::spawn(mine_and_submit_solution::( + tx.clone(), + at, + api.clone(), + signer.clone(), + config.clone(), + submit_lock.clone(), + )); + + let account_info = { + let addr = runtime::storage().system().account(signer.account_id()); + api.storage().fetch(&addr, None).await?.ok_or(Error::AccountDoesNotExists)? + }; + // this is lossy but fine for now. + prometheus::set_balance(account_info.data.free as f64); + } +} - let signer = Signer::new(&config.seed_or_path)?; +fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender, err: Error) { + match err { + Error::AlreadySubmitted + | Error::BetterScoreExist + | Error::IncorrectPhase + | Error::TransactionRejected(_) + | Error::SubscriptionClosed => {}, + err => { + let _ = tx.send(err); + }, + } +} - let account_info = { - let addr = runtime::storage().system().account(signer.account_id()); - api.storage().fetch(&addr, None).await?.ok_or(Error::AccountDoesNotExists)? - }; +/// Construct extrinsic at given block and watch it. +async fn mine_and_submit_solution( + tx: tokio::sync::mpsc::UnboundedSender, + at: Header, + api: SubxtClient, + mut signer: Signer, + config: MonitorConfig, + submit_lock: Arc>, +) where + T: MinerConfig + + Send + + Sync + + 'static, + T::Solution: Send, +{ + let hash = at.hash(); + log::trace!(target: LOG_TARGET, "new event at #{:?} ({:?})", at.number, hash); + + // NOTE: as we try to send at each block then the nonce is used guard against + // submitting twice. Because once a solution has been accepted on chain + // the "next transaction" at a later block but with the same nonce will be rejected + let nonce = match api.rpc().system_account_next_index(signer.account_id()).await { + Ok(none) => none, + Err(e) => { + kill_main_task_if_critical_err(&tx, e.into()); + return; + }, + }; + signer.set_nonce(nonce); + + if let Err(e) = ensure_signed_phase(&api, hash).await { + log::debug!( + target: LOG_TARGET, + "ensure_signed_phase failed: {:?}; skipping block: {}", + e, + at.number + ); + + kill_main_task_if_critical_err(&tx, e); + return; + } - log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer, account_info); - - let mut subscription = heads_subscription(&api, config.listen).await?; - let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); - let submit_lock = Arc::new(Mutex::new(())); - - loop { - let at = tokio::select! { - maybe_rp = subscription.next() => { - match maybe_rp { - Some(Ok(r)) => r, - Some(Err(e)) => { - log::error!(target: LOG_TARGET, "subscription failed to decode Header {:?}, this is bug please file an issue", e); - return Err(e.into()); - } - // The subscription was dropped, should only happen if: - // - the connection was closed. - // - the subscription could not keep up with the server. - None => { - log::warn!(target: LOG_TARGET, "subscription to `{:?}` terminated. Retrying..", config.listen); - subscription = heads_subscription(&api, config.listen).await?; - continue - } - } - }, - maybe_err = rx.recv() => { - match maybe_err { - Some(err) => return Err(err), - None => unreachable!("at least one sender kept in the main loop should always return Some; qed"), - } - } - }; - - // Spawn task and non-recoverable errors are sent back to the main task - // such as if the connection has been closed. - tokio::spawn(mine_and_submit_solution( - tx.clone(), - at, - api.clone(), - signer.clone(), - config.clone(), - submit_lock.clone(), - )); - - let account_info = { - let addr = runtime::storage().system().account(signer.account_id()); - api.storage().fetch(&addr, None).await?.ok_or(Error::AccountDoesNotExists)? - }; - // this is lossy but fine for now. - prometheus::set_balance(account_info.data.free as f64); - } + let round = match api + .storage() + .fetch(&runtime::storage().election_provider_multi_phase().round(), Some(hash)) + .await + { + Ok(Some(round)) => round, + // Default round is 1 + // https://github.com/paritytech/substrate/blob/49b06901eb65f2c61ff0934d66987fd955d5b8f5/frame/election-provider-multi-phase/src/lib.rs#L1188 + Ok(None) => 1, + Err(e) => { + log::error!(target: LOG_TARGET, "Mining solution failed: {:?}", e); + kill_main_task_if_critical_err(&tx, e.into()); + return; + }, + }; - /// Construct extrinsic at given block and watch it. - async fn mine_and_submit_solution( - tx: tokio::sync::mpsc::UnboundedSender, - at: Header, - api: SubxtClient, - mut signer: Signer, - config: MonitorConfig, - submit_lock: Arc>, - ) { - - let hash = at.hash(); - log::trace!(target: LOG_TARGET, "new event at #{:?} ({:?})", at.number, hash); - - // NOTE: as we try to send at each block then the nonce is used guard against - // submitting twice. Because once a solution has been accepted on chain - // the "next transaction" at a later block but with the same nonce will be rejected - let nonce = match api.rpc().system_account_next_index(signer.account_id()).await { - Ok(none) => none, - Err(e) => { - kill_main_task_if_critical_err(&tx, e.into()); - return; - } - }; - signer.set_nonce(nonce); - - if let Err(e) = ensure_signed_phase(&api, hash).await { - log::debug!( - target: LOG_TARGET, - "ensure_signed_phase failed: {:?}; skipping block: {}", - e, - at.number - ); + let _lock = submit_lock.lock().await; - kill_main_task_if_critical_err(&tx, e); - return; - } + if let Err(e) = ensure_no_previous_solution(&api, hash, signer.account_id()).await { + log::debug!( + target: LOG_TARGET, + "ensure_no_previous_solution failed: {:?}; skipping block: {}", + e, + at.number + ); - let round = match api.storage().fetch(&runtime::storage().election_provider_multi_phase().round(), Some(hash)).await { - Ok(Some(round)) => round, - // Default round is 1 - // https://github.com/paritytech/substrate/blob/49b06901eb65f2c61ff0934d66987fd955d5b8f5/frame/election-provider-multi-phase/src/lib.rs#L1188 - Ok(None) => 1, - Err(e) => { - log::error!(target: LOG_TARGET, "Mining solution failed: {:?}", e); - kill_main_task_if_critical_err(&tx, e.into()); - return; - }, - }; - - let _lock = submit_lock.lock().await; - - if let Err(e) = ensure_no_previous_solution(&api, hash, signer.account_id()).await { - log::debug!( - target: LOG_TARGET, - "ensure_no_previous_solution failed: {:?}; skipping block: {}", - e, - at.number - ); + kill_main_task_if_critical_err(&tx, e); + return; + } - kill_main_task_if_critical_err(&tx, e); - return; - } + let (solution, score) = + match helpers::mine_solution::(&api, Some(hash), config.solver).timed().await { + (Ok((solution, score, size)), elapsed) => { + let elapsed_ms = elapsed.as_millis(); + let encoded_len = solution.encoded_size(); + let active_voters = solution.voter_count() as u32; + let desired_targets = solution.unique_targets().len() as u32; + + let final_weight = tokio::task::spawn_blocking(move || { + T::solution_weight(size.voters, size.targets, active_voters, desired_targets) + }) + .await + .unwrap(); + + log::info!( + target: LOG_TARGET, + "Mined solution with {:?} size: {:?} round: {:?} at: {}, took: {} ms, len: {:?}, weight = {:?}", + score, + size, + round, + at.number(), + elapsed_ms, + encoded_len, + final_weight, + ); + + prometheus::set_length(encoded_len); + prometheus::set_weight(final_weight); + prometheus::observe_mined_solution_duration(elapsed_ms as f64); + prometheus::set_score(score); + + (solution, score) + }, + (Err(e), _) => { + kill_main_task_if_critical_err(&tx, e); + return; + }, + }; + + let best_head = match get_latest_head(&api, config.listen).await { + Ok(head) => head, + Err(e) => { + kill_main_task_if_critical_err(&tx, e); + return; + }, + }; + + if let Err(e) = ensure_signed_phase(&api, best_head).await { + log::debug!( + target: LOG_TARGET, + "ensure_signed_phase failed: {:?}; skipping block: {}", + e, + at.number + ); + kill_main_task_if_critical_err(&tx, e); + return; + } - let (solution, score) = - match helpers::[](&api, Some(hash), config.solver).timed().await { - (Ok((solution, score, size)), elapsed) => { - let elapsed_ms = elapsed.as_millis(); - let encoded_len = solution.encoded_size(); - let active_voters = solution.voter_count() as u32; - let desired_targets = solution.unique_targets().len() as u32; - - let final_weight = tokio::task::spawn_blocking(move || { - chain::MinerConfig::solution_weight( - size.voters, - size.targets, - active_voters, - desired_targets - ) - }).await.unwrap(); - - log::info!( - target: LOG_TARGET, - "Mined solution with {:?} size: {:?} round: {:?} at: {}, took: {} ms, len: {:?}, weight = {:?}", - score, - size, - round, - at.number(), - elapsed_ms, - encoded_len, - final_weight, - ); - - prometheus::set_length(encoded_len); - prometheus::set_weight(final_weight); - prometheus::observe_mined_solution_duration(elapsed_ms as f64); - prometheus::set_score(score); - - (solution, score) - } - (Err(e), _) => { - kill_main_task_if_critical_err(&tx, e); - return; - } - }; - - let best_head = match get_latest_head(&api, config.listen).await { - Ok(head) => head, - Err(e) => { - kill_main_task_if_critical_err(&tx, e); - return; - } - }; - - if let Err(e) = ensure_signed_phase(&api, best_head).await { - log::debug!( - target: LOG_TARGET, - "ensure_signed_phase failed: {:?}; skipping block: {}", - e, - at.number - ); - kill_main_task_if_critical_err(&tx, e); - return; - } + match ensure_no_better_solution(&api, best_head, score, config.submission_strategy) + .timed() + .await + { + (Ok(_), now) => { + log::trace!( + target: LOG_TARGET, + "Solution validity verification took: {} ms", + now.as_millis() + ); + }, + (Err(e), _) => { + log::debug!( + target: LOG_TARGET, + "ensure_no_better_solution failed: {:?}; skipping block: {}", + e, + at.number + ); + kill_main_task_if_critical_err(&tx, e); + return; + }, + }; + + prometheus::on_submission_attempt(); + match submit_and_watch_solution::(&api, signer, (solution, score, round), hash) + .timed() + .await + { + (Ok(_), now) => { + prometheus::on_submission_success(); + prometheus::observe_submit_and_watch_duration(now.as_millis() as f64); + }, + (Err(e), _) => { + log::warn!( + target: LOG_TARGET, + "submit_and_watch_solution failed: {:?}; skipping block: {}", + e, + at.number + ); + kill_main_task_if_critical_err(&tx, e); + }, + }; +} - match ensure_no_better_solution(&api, best_head, score, config.submission_strategy).timed().await { - (Ok(_), now) => { - log::trace!(target: LOG_TARGET, "Solution validity verification took: {} ms", now.as_millis()); - } - (Err(e), _) => { - log::debug!( - target: LOG_TARGET, - "ensure_no_better_solution failed: {:?}; skipping block: {}", - e, - at.number - ); - kill_main_task_if_critical_err(&tx, e); - return; - } - }; - - prometheus::on_submission_attempt(); - match submit_and_watch_solution(&api, signer, (solution, score, round), hash).timed().await { - (Ok(_), now) => { - prometheus::on_submission_success(); - prometheus::observe_submit_and_watch_duration(now.as_millis() as f64); - } - (Err(e), _) => { - log::warn!( - target: LOG_TARGET, - "submit_and_watch_solution failed: {:?}; skipping block: {}", - e, - at.number - ); - kill_main_task_if_critical_err(&tx, e); - } - }; - } +/// Ensure that now is the signed phase. +async fn ensure_signed_phase(api: &SubxtClient, hash: Hash) -> Result<(), Error> { + use pallet_election_provider_multi_phase::Phase; - /// Ensure that now is the signed phase. - async fn ensure_signed_phase(api: &SubxtClient, hash: Hash) -> Result<(), Error> { - use pallet_election_provider_multi_phase::Phase; + let addr = runtime::storage().election_provider_multi_phase().current_phase(); + let res = api.storage().fetch(&addr, Some(hash)).await; - let addr = chain::runtime::storage().election_provider_multi_phase().current_phase(); - let res = api.storage().fetch(&addr, Some(hash)).await; + match res { + Ok(Some(Phase::Signed)) => Ok(()), + Ok(Some(_)) => Err(Error::IncorrectPhase), + // Default phase is None + // https://github.com/paritytech/substrate/blob/49b06901eb65f2c61ff0934d66987fd955d5b8f5/frame/election-provider-multi-phase/src/lib.rs#L1193 + Ok(None) => Err(Error::IncorrectPhase), + Err(e) => Err(e.into()), + } +} - match res { - Ok(Some(Phase::Signed)) => Ok(()), - Ok(Some(_)) => Err(Error::IncorrectPhase), - // Default phase is None - // https://github.com/paritytech/substrate/blob/49b06901eb65f2c61ff0934d66987fd955d5b8f5/frame/election-provider-multi-phase/src/lib.rs#L1193 - Ok(None) => Err(Error::IncorrectPhase), - Err(e) => Err(e.into()), - } - } +/// Ensure that our current `us` have not submitted anything previously. +async fn ensure_no_previous_solution( + api: &SubxtClient, + at: Hash, + us: &AccountId, +) -> Result<(), Error> { + let addr = runtime::storage().election_provider_multi_phase().signed_submission_indices(); + let indices = api.storage().fetch_or_default(&addr, Some(at)).await?; - /// Ensure that our current `us` have not submitted anything previously. - async fn ensure_no_previous_solution( - api: &SubxtClient, - at: Hash, - us: &AccountId, - ) -> Result<(), Error> { - - let addr = chain::runtime::storage().election_provider_multi_phase().signed_submission_indices(); - let indices = api - .storage() - .fetch_or_default(&addr, Some(at)) - .await?; - - for (_score, idx) in indices.0 { - let addr = runtime::storage().election_provider_multi_phase().signed_submissions_map(&idx); - - let submission = api - .storage() - .fetch(&addr, Some(at)) - .await?; - - if let Some(submission) = submission { - if &submission.who == us { - return Err(Error::AlreadySubmitted); - } - } - } + for (_score, idx) in indices.0 { + let addr = runtime::storage().election_provider_multi_phase().signed_submissions_map(&idx); - Ok(()) - } + let submission = api.storage().fetch(&addr, Some(at)).await?; - async fn ensure_no_better_solution( - api: &SubxtClient, - at: Hash, - score: sp_npos_elections::ElectionScore, - strategy: SubmissionStrategy, - ) -> Result<(), Error> { - let epsilon = match strategy { - // don't care about current scores. - SubmissionStrategy::Always => return Ok(()), - SubmissionStrategy::IfLeading => Perbill::zero(), - SubmissionStrategy::ClaimBetterThan(epsilon) => epsilon, - }; - - let addr = runtime::storage().election_provider_multi_phase().signed_submission_indices(); - let indices = api - .storage() - .fetch_or_default(&addr, Some(at)) - .await?; - - log::debug!(target: LOG_TARGET, "submitted solutions: {:?}", indices.0); - - for (other_score, _) in indices.0 { - if !score.strict_threshold_better(other_score, epsilon) { - return Err(Error::BetterScoreExist); - } - } + if let Some(submission) = submission { + if &submission.who == us { + return Err(Error::AlreadySubmitted); + } + } + } - Ok(()) - } + Ok(()) +} - async fn submit_and_watch_solution( - api: &SubxtClient, - signer: Signer, - (solution, score, round): (SolutionOf, sp_npos_elections::ElectionScore, u32), - hash: Hash - ) -> Result<(), Error> { - - let tx = runtime::tx().election_provider_multi_phase().submit(RawSolution { solution, score, round }); - - let mut status_sub = api.tx().sign_and_submit_then_watch_default(&tx, &*signer).await.map_err(|e| { - log::warn!(target: LOG_TARGET, "submit solution failed: {:?}", e); - e - })?; - - loop { - let status = match status_sub.next_item().await { - Some(Ok(status)) => status, - Some(Err(err)) => { - log::error!( - target: LOG_TARGET, - "watch submit extrinsic at {:?} failed: {:?}", - hash, - err - ); - return Err(err.into()); - }, - None => { - return Err(Error::SubscriptionClosed); - }, - }; - - match status { - TxStatus::Ready | TxStatus::Broadcast(_) | TxStatus::Future => (), - TxStatus::InBlock(details) => { - - let events = details.fetch_events().await.expect("events should exist"); - - let solution_stored = events.find_first::(); - - return if let Ok(Some(_)) = solution_stored { - log::info!(target: LOG_TARGET, "Included at {:?}", details.block_hash()); - Ok(()) - } else { - Err(Error::Other(format!("No SolutionStored event found at {:?}", details.block_hash()))) - }; - }, - TxStatus::Retracted(hash) => { - log::info!(target: LOG_TARGET, "Retracted at {:?}", hash); - }, - TxStatus::Finalized(details) => { - log::info!(target: LOG_TARGET, "Finalized at {:?}", details.block_hash()); - return Ok(()); - } - _ => { - return Err(Error::TransactionRejected(format!("{:?}", status))); - }, - } - } - } - } +async fn ensure_no_better_solution( + api: &SubxtClient, + at: Hash, + score: sp_npos_elections::ElectionScore, + strategy: SubmissionStrategy, +) -> Result<(), Error> { + let epsilon = match strategy { + // don't care about current scores. + SubmissionStrategy::Always => return Ok(()), + SubmissionStrategy::IfLeading => Perbill::zero(), + SubmissionStrategy::ClaimBetterThan(epsilon) => epsilon, + }; + + let addr = runtime::storage().election_provider_multi_phase().signed_submission_indices(); + let indices = api.storage().fetch_or_default(&addr, Some(at)).await?; + + log::debug!(target: LOG_TARGET, "submitted solutions: {:?}", indices.0); + + for (other_score, _) in indices.0 { + if !score.strict_threshold_better(other_score, epsilon) { + return Err(Error::BetterScoreExist); } } + + Ok(()) } -#[cfg(feature = "polkadot")] -monitor_cmd_for!(polkadot); -#[cfg(feature = "kusama")] -monitor_cmd_for!(kusama); -#[cfg(feature = "westend")] -monitor_cmd_for!(westend); +async fn submit_and_watch_solution( + api: &SubxtClient, + signer: Signer, + (solution, score, round): (SolutionOf, sp_npos_elections::ElectionScore, u32), + hash: Hash, +) -> Result<(), Error> { + let raw = RawSolution { solution, score, round }.encode(); + + let tx = + subxt::dynamic::tx("ElectionProviderMultiPhase", "submit", vec![Value::from_bytes(&raw)]); + + let mut status_sub = + api.tx().sign_and_submit_then_watch_default(&tx, &*signer).await.map_err(|e| { + log::warn!(target: LOG_TARGET, "submit solution failed: {:?}", e); + e + })?; + + loop { + let status = match status_sub.next_item().await { + Some(Ok(status)) => status, + Some(Err(err)) => { + log::error!( + target: LOG_TARGET, + "watch submit extrinsic at {:?} failed: {:?}", + hash, + err + ); + return Err(err.into()); + }, + None => { + return Err(Error::SubscriptionClosed); + }, + }; + + match status { + TxStatus::Ready | TxStatus::Broadcast(_) | TxStatus::Future => (), + TxStatus::InBlock(details) => { + let events = details.fetch_events().await.expect("events should exist"); + + let solution_stored = + events + .find_first::( + ); -fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender, err: Error) { - match err { - Error::AlreadySubmitted | - Error::BetterScoreExist | - Error::IncorrectPhase | - Error::TransactionRejected(_) | - Error::SubscriptionClosed => {}, - err => { - let _ = tx.send(err); - }, + return if let Ok(Some(_)) = solution_stored { + log::info!(target: LOG_TARGET, "Included at {:?}", details.block_hash()); + Ok(()) + } else { + Err(Error::Other(format!( + "No SolutionStored event found at {:?}", + details.block_hash() + ))) + }; + }, + TxStatus::Retracted(hash) => { + log::info!(target: LOG_TARGET, "Retracted at {:?}", hash); + }, + TxStatus::Finalized(details) => { + log::info!(target: LOG_TARGET, "Finalized at {:?}", details.block_hash()); + return Ok(()); + }, + _ => { + return Err(Error::TransactionRejected(format!("{:?}", status))); + }, + } } } diff --git a/src/opt.rs b/src/opt.rs index a65d90d3a..de555f63d 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -34,61 +34,22 @@ macro_rules! any_runtime { ($chain:tt, $($code:tt)*) => { match $chain { Chain::Polkadot => { - #[cfg(feature = "polkadot")] - { - #[allow(unused)] - use { - $crate::monitor::run_polkadot as monitor_cmd, - $crate::dry_run::run_polkadot as dry_run_cmd, - $crate::emergency_solution::run_polkadot as emergency_cmd, - $crate::chain::polkadot::runtime - }; - $($code)* - } - - #[cfg(not(feature = "polkadot"))] - { - panic!("polkadot feature is not enabled, but target chain is polkadot") - } + #[allow(unused)] + use $crate::chain::polkadot::MinerConfig; + $($code)* }, Chain::Kusama => { - #[cfg(feature = "kusama")] - { - #[allow(unused)] - use { - $crate::monitor::run_kusama as monitor_cmd, - $crate::dry_run::run_kusama as dry_run_cmd, - $crate::emergency_solution::run_kusama as emergency_cmd, - $crate::chain::kusama::runtime - }; - $($code)* - } - - #[cfg(not(feature = "kusama"))] - { - panic!("kusama feature is not enabled, but target chain is kusama") - } + #[allow(unused)] + use $crate::chain::kusama::MinerConfig; + $($code)* }, Chain::Westend => { - #[cfg(feature = "westend")] - { - #[allow(unused)] - use { - $crate::monitor::run_westend as monitor_cmd, - $crate::dry_run::run_westend as dry_run_cmd, - $crate::emergency_solution::run_westend as emergency_cmd, - $crate::chain::westend::runtime - }; - $($code)* - } - - #[cfg(not(feature = "westend"))] - { - panic!("westend feature is not enabled, but target chain is westend") - } - } + #[allow(unused)] + use $crate::chain::westend::MinerConfig; + $($code)* + }, } - } + }; } /// Submission strategy to use. @@ -125,7 +86,7 @@ impl FromStr for SubmissionStrategy { let percent: u32 = percent.parse().map_err(|e| format!("{:?}", e))?; Self::ClaimBetterThan(Perbill::from_percent(percent)) } else { - return Err(s.into()) + return Err(s.into()); }; Ok(res) } diff --git a/src/prelude.rs b/src/prelude.rs index f56e91fbf..cade05d6a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -63,3 +63,28 @@ pub type SubxtClient = subxt::OnlineClient; /// Config used by the staking-miner pub type Config = subxt::PolkadotConfig; + +#[subxt::subxt( + runtime_metadata_path = "artifacts/metadata.scale", + derive_for_all_types = "Clone, Debug, Eq, PartialEq", + derive_for_type( + type = "pallet_election_provider_multi_phase::RoundSnapshot", + derive = "Default" + ) +)] +pub mod runtime { + #[subxt(substitute_type = "sp_arithmetic::per_things::PerU16")] + use ::sp_runtime::PerU16; + + #[subxt(substitute_type = "pallet_election_provider_multi_phase::RawSolution")] + use ::pallet_election_provider_multi_phase::RawSolution; + + #[subxt(substitute_type = "sp_npos_elections::ElectionScore")] + use ::sp_npos_elections::ElectionScore; + + #[subxt(substitute_type = "pallet_election_provider_multi_phase::Phase")] + use ::pallet_election_provider_multi_phase::Phase; + + #[subxt(substitute_type = "pallet_election_provider_multi_phase::SolutionOrSnapshotSize")] + use ::pallet_election_provider_multi_phase::SolutionOrSnapshotSize; +} From 8735de9fb5eb014e24edae40323c13d7bbd3279d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 30 Sep 2022 10:59:25 +0200 Subject: [PATCH 13/18] fix: decode scale value correctly --- src/chain.rs | 6 +++-- src/dry_run.rs | 2 +- src/error.rs | 2 ++ src/helpers.rs | 60 +++++++++++++++++++++++++++++++++----------------- src/main.rs | 13 +++++------ src/monitor.rs | 53 +++++++++++++++++++------------------------- src/opt.rs | 2 +- 7 files changed, 77 insertions(+), 61 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index efd2c2508..1270d59c9 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -13,6 +13,7 @@ use frame_support::{traits::ConstU32, weights::Weight}; use once_cell::sync::OnceCell; use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; use pallet_transaction_payment::RuntimeDispatchInfo; +use scale_info::TypeInfo; use sp_core::Bytes; use subxt::rpc::rpc_params; @@ -171,11 +172,12 @@ pub mod kusama { /// Helper to fetch the weight from a remote node /// /// Panics: if the RPC call fails or if decoding the response as a `Weight` fails. -fn get_weight( +fn get_weight( raw_solution: RawSolution, witness: SolutionOrSnapshotSize, ) -> Weight { - let tx = unsigned_solution_tx(raw_solution, witness); + let tx = + unsigned_solution_tx(raw_solution, witness).expect("Failed to create dynamic transaction"); futures::executor::block_on(async { let client = SHARED_CLIENT.get().expect("shared client is configured as start; qed"); diff --git a/src/dry_run.rs b/src/dry_run.rs index bba087b8f..a9d57c82e 100644 --- a/src/dry_run.rs +++ b/src/dry_run.rs @@ -65,7 +65,7 @@ where raw_solution.encode().len(), ); - let tx = signed_solution_tx(raw_solution); + let tx = signed_solution_tx(raw_solution)?; let xt = api.tx().create_signed(&tx, &*signer, ExtrinsicParams::default()).await?; let outcome = api.rpc().dry_run(xt.encoded(), config.at).await?; diff --git a/src/error.rs b/src/error.rs index ae61c71d5..394b411d4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,4 +28,6 @@ pub enum Error { TransactionRejected(String), #[error("Subscription closed")] SubscriptionClosed, + #[error("Dynamic transaction error: {0}")] + DynamicTransaction(String), } diff --git a/src/helpers.rs b/src/helpers.rs index 8a73f74c8..4f8386195 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -5,6 +5,8 @@ use frame_support::{weights::Weight, BoundedVec}; use pallet_election_provider_multi_phase::{RawSolution, SolutionOf, SolutionOrSnapshotSize}; use pin_project_lite::pin_project; use runtime::runtime_types::pallet_election_provider_multi_phase::RoundSnapshot; +use scale_info::{PortableRegistry, TypeInfo}; +use scale_value::scale::{decode_as_type, DecodeError, TypeId}; use sp_npos_elections::ElectionScore; use std::{ future::Future, @@ -12,8 +14,7 @@ use std::{ task::{Context, Poll}, time::{Duration, Instant}, }; -use subxt::dynamic::Value; -use subxt::tx::DynamicTxPayload; +use subxt::{dynamic::Value, tx::DynamicTxPayload}; pub type BoundedVoters = Vec<(AccountId, VoteWeight, BoundedVec)>; @@ -205,30 +206,49 @@ fn read_constant<'a, T: serde::Deserialize<'a>>( }) } -pub fn signed_solution_tx( +pub fn signed_solution_tx( solution: RawSolution, -) -> DynamicTxPayload<'static> { - let encoded_solution = solution.encode(); +) -> Result, Error> { + let scale_solution = to_scale_value(solution).map_err(|e| { + Error::DynamicTransaction(format!("Failed to decode `RawSolution`: {:?}", e)) + })?; - subxt::dynamic::tx( - "ElectionProviderMultiPhase", - "submit", - vec![Value::from_bytes(&encoded_solution)], - ) + Ok(subxt::dynamic::tx("ElectionProviderMultiPhase", "submit", vec![scale_solution])) } -pub fn unsigned_solution_tx( +pub fn unsigned_solution_tx( solution: RawSolution, witness: SolutionOrSnapshotSize, -) -> DynamicTxPayload<'static> { - let encoded_solution = solution.encode(); - let encoded_witness = witness.encode(); - - let x = Value::from(encoded_solution); - - subxt::dynamic::tx( +) -> Result, Error> { + let scale_solution = to_scale_value(solution).map_err(|e| { + Error::DynamicTransaction(format!("Failed to decode `RawSolution`: {:?}", e)) + })?; + let scale_witness = to_scale_value(witness).map_err(|e| { + Error::DynamicTransaction(format!("Failed to decode `SolutionOrSnapshotSize`: {:?}", e)) + })?; + + Ok(subxt::dynamic::tx( "ElectionProviderMultiPhase", "submit_unsigned", - vec![Value::from_bytes(&encoded_solution), Value::from_bytes(&encoded_witness)], - ) + vec![scale_solution, scale_witness], + )) +} + +fn make_type() -> (TypeId, PortableRegistry) { + let m = scale_info::MetaType::new::(); + let mut types = scale_info::Registry::new(); + let id = types.register_type(&m); + let portable_registry: PortableRegistry = types.into(); + + (id.into(), portable_registry) +} + +fn to_scale_value( + val: T, +) -> Result { + let (ty_id, types) = make_type::(); + + let bytes = val.encode(); + + decode_as_type(&mut bytes.as_ref(), ty_id, &types).map(|v| v.remove_context()) } diff --git a/src/main.rs b/src/main.rs index 7b618b9bc..8a51063b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,9 +93,8 @@ async fn main() -> Result<(), Error> { let fut = match command { Command::Monitor(cfg) => monitor::monitor_cmd::(api, cfg).boxed(), Command::DryRun(cfg) => dry_run::dry_run_cmd::(api, cfg).boxed(), - Command::EmergencySolution(cfg) => { - emergency_solution::emergency_cmd::(api, cfg).boxed() - }, + Command::EmergencySolution(cfg) => + emergency_solution::emergency_cmd::(api, cfg).boxed(), }; run_command(fut, rx_upgrade).await @@ -158,7 +157,7 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(u) => u, Err(e) => { let _ = tx.send(e.into()); - return; + return }, }; @@ -172,10 +171,10 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(u) => u, Err(e) => { let _ = tx.send(e.into()); - return; + return }, }; - continue; + continue }, }; @@ -184,7 +183,7 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { Ok(()) => { if let Err(e) = helpers::read_metadata_constants(&api).await { let _ = tx.send(e.into()); - return; + return } prometheus::on_runtime_upgrade(); log::info!(target: LOG_TARGET, "upgrade to version: {} successful", version); diff --git a/src/monitor.rs b/src/monitor.rs index 86958bc5f..0822e98b9 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,6 +1,6 @@ use crate::{ error::Error, - helpers::{self, TimedFuture}, + helpers::{self, signed_solution_tx, TimedFuture}, opt::{Listen, MonitorConfig, SubmissionStrategy}, prelude::*, prometheus, @@ -12,7 +12,7 @@ use frame_election_provider_support::NposSolution; use pallet_election_provider_multi_phase::{RawSolution, SolutionOf}; use sp_runtime::Perbill; use std::sync::Arc; -use subxt::{dynamic::Value, rpc::Subscription, tx::TxStatus}; +use subxt::{rpc::Subscription, tx::TxStatus}; use tokio::sync::Mutex; pub async fn monitor_cmd(api: SubxtClient, config: MonitorConfig) -> Result<(), Error> @@ -85,11 +85,11 @@ where fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender, err: Error) { match err { - Error::AlreadySubmitted - | Error::BetterScoreExist - | Error::IncorrectPhase - | Error::TransactionRejected(_) - | Error::SubscriptionClosed => {}, + Error::AlreadySubmitted | + Error::BetterScoreExist | + Error::IncorrectPhase | + Error::TransactionRejected(_) | + Error::SubscriptionClosed => {}, err => { let _ = tx.send(err); }, @@ -121,7 +121,7 @@ async fn mine_and_submit_solution( Ok(none) => none, Err(e) => { kill_main_task_if_critical_err(&tx, e.into()); - return; + return }, }; signer.set_nonce(nonce); @@ -135,7 +135,7 @@ async fn mine_and_submit_solution( ); kill_main_task_if_critical_err(&tx, e); - return; + return } let round = match api @@ -150,7 +150,7 @@ async fn mine_and_submit_solution( Err(e) => { log::error!(target: LOG_TARGET, "Mining solution failed: {:?}", e); kill_main_task_if_critical_err(&tx, e.into()); - return; + return }, }; @@ -165,7 +165,7 @@ async fn mine_and_submit_solution( ); kill_main_task_if_critical_err(&tx, e); - return; + return } let (solution, score) = @@ -203,7 +203,7 @@ async fn mine_and_submit_solution( }, (Err(e), _) => { kill_main_task_if_critical_err(&tx, e); - return; + return }, }; @@ -211,7 +211,7 @@ async fn mine_and_submit_solution( Ok(head) => head, Err(e) => { kill_main_task_if_critical_err(&tx, e); - return; + return }, }; @@ -223,7 +223,7 @@ async fn mine_and_submit_solution( at.number ); kill_main_task_if_critical_err(&tx, e); - return; + return } match ensure_no_better_solution(&api, best_head, score, config.submission_strategy) @@ -245,7 +245,7 @@ async fn mine_and_submit_solution( at.number ); kill_main_task_if_critical_err(&tx, e); - return; + return }, }; @@ -303,7 +303,7 @@ async fn ensure_no_previous_solution( if let Some(submission) = submission { if &submission.who == us { - return Err(Error::AlreadySubmitted); + return Err(Error::AlreadySubmitted) } } } @@ -331,7 +331,7 @@ async fn ensure_no_better_solution( for (other_score, _) in indices.0 { if !score.strict_threshold_better(other_score, epsilon) { - return Err(Error::BetterScoreExist); + return Err(Error::BetterScoreExist) } } @@ -344,10 +344,7 @@ async fn submit_and_watch_solution( (solution, score, round): (SolutionOf, sp_npos_elections::ElectionScore, u32), hash: Hash, ) -> Result<(), Error> { - let raw = RawSolution { solution, score, round }.encode(); - - let tx = - subxt::dynamic::tx("ElectionProviderMultiPhase", "submit", vec![Value::from_bytes(&raw)]); + let tx = signed_solution_tx(RawSolution { solution, score, round })?; let mut status_sub = api.tx().sign_and_submit_then_watch_default(&tx, &*signer).await.map_err(|e| { @@ -365,11 +362,9 @@ async fn submit_and_watch_solution( hash, err ); - return Err(err.into()); - }, - None => { - return Err(Error::SubscriptionClosed); + return Err(err.into()) }, + None => return Err(Error::SubscriptionClosed), }; match status { @@ -390,18 +385,16 @@ async fn submit_and_watch_solution( "No SolutionStored event found at {:?}", details.block_hash() ))) - }; + } }, TxStatus::Retracted(hash) => { log::info!(target: LOG_TARGET, "Retracted at {:?}", hash); }, TxStatus::Finalized(details) => { log::info!(target: LOG_TARGET, "Finalized at {:?}", details.block_hash()); - return Ok(()); - }, - _ => { - return Err(Error::TransactionRejected(format!("{:?}", status))); + return Ok(()) }, + _ => return Err(Error::TransactionRejected(format!("{:?}", status))), } } } diff --git a/src/opt.rs b/src/opt.rs index de555f63d..575534302 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -86,7 +86,7 @@ impl FromStr for SubmissionStrategy { let percent: u32 = percent.parse().map_err(|e| format!("{:?}", e))?; Self::ClaimBetterThan(Perbill::from_percent(percent)) } else { - return Err(s.into()); + return Err(s.into()) }; Ok(res) } From 1290d8c7717ed2dcd8eb8ade0ba3e06b92b9126d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 30 Sep 2022 17:28:20 +0200 Subject: [PATCH 14/18] make SubmissionsMap dynamic + cleanup --- src/chain.rs | 228 -------------------------------------------- src/dry_run.rs | 10 +- src/error.rs | 2 + src/helpers.rs | 140 +++------------------------ src/lib.rs | 2 +- src/main.rs | 8 +- src/monitor.rs | 22 +++-- src/opt.rs | 6 +- src/prelude.rs | 14 +++ src/static_types.rs | 163 +++++++++++++++++++++++++++++++ 10 files changed, 214 insertions(+), 381 deletions(-) delete mode 100644 src/chain.rs diff --git a/src/chain.rs b/src/chain.rs deleted file mode 100644 index 1270d59c9..000000000 --- a/src/chain.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Chain specific types for polkadot, kusama and westend. - -// A big chunk of this file, annotated with `SYNC` should be in sync with the values that are used in -// the real runtimes. Unfortunately, no way to get around them now. -// TODO: There might be a way to create a new crate called `polkadot-runtime-configs` in -// polkadot, that only has `const` and `type`s that are used in the runtime, and we can import -// that. - -use crate::{helpers::unsigned_solution_tx, prelude::*, static_types}; -use codec::{Decode, Encode}; -use frame_election_provider_support::traits::NposSolution; -use frame_support::{traits::ConstU32, weights::Weight}; -use once_cell::sync::OnceCell; -use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; -use pallet_transaction_payment::RuntimeDispatchInfo; -use scale_info::TypeInfo; -use sp_core::Bytes; -use subxt::rpc::rpc_params; - -pub static SHARED_CLIENT: OnceCell = OnceCell::new(); - -pub mod westend { - use super::*; - - // SYNC - frame_election_provider_support::generate_solution_type!( - #[compact] - pub struct NposSolution16::< - VoterIndex = u32, - TargetIndex = u16, - Accuracy = sp_runtime::PerU16, - MaxVoters = ConstU32::<22500> - >(16) - ); - - #[derive(Debug)] - pub struct MinerConfig; - impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig { - type AccountId = AccountId; - type MaxLength = static_types::MaxLength; - type MaxWeight = static_types::MaxWeight; - type MaxVotesPerVoter = static_types::MaxVotesPerVoter; - type Solution = NposSolution16; - - // SYNC - fn solution_weight( - voters: u32, - targets: u32, - active_voters: u32, - desired_targets: u32, - ) -> Weight { - // Mock a RawSolution to get the correct weight without having to do the heavy work. - let raw_solution = RawSolution { - solution: NposSolution16 { - votes1: mock_votes( - active_voters, - desired_targets.try_into().expect("Desired targets < u16::MAX"), - ), - ..Default::default() - }, - ..Default::default() - }; - - assert_eq!(raw_solution.solution.voter_count(), active_voters as usize); - assert_eq!(raw_solution.solution.unique_targets().len(), desired_targets as usize); - - get_weight(raw_solution, SolutionOrSnapshotSize { voters, targets }) - } - } -} - -pub mod polkadot { - use super::*; - - // SYNC - frame_election_provider_support::generate_solution_type!( - #[compact] - pub struct NposSolution16::< - VoterIndex = u32, - TargetIndex = u16, - Accuracy = sp_runtime::PerU16, - MaxVoters = ConstU32::<22500> - >(16) - ); - - #[derive(Debug)] - pub struct MinerConfig; - impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig { - type AccountId = AccountId; - type MaxLength = static_types::MaxLength; - type MaxWeight = static_types::MaxWeight; - type MaxVotesPerVoter = static_types::MaxVotesPerVoter; - type Solution = NposSolution16; - - // SYNC - fn solution_weight( - voters: u32, - targets: u32, - active_voters: u32, - desired_targets: u32, - ) -> Weight { - // Mock a RawSolution to get the correct weight without having to do the heavy work. - let raw = RawSolution { - solution: NposSolution16 { - votes1: mock_votes( - active_voters, - desired_targets.try_into().expect("Desired targets < u16::MAX"), - ), - ..Default::default() - }, - ..Default::default() - }; - - assert_eq!(raw.solution.voter_count(), active_voters as usize); - assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); - - get_weight(raw, SolutionOrSnapshotSize { voters, targets }) - } - } -} - -pub mod kusama { - use super::*; - - // SYNC - frame_election_provider_support::generate_solution_type!( - #[compact] - pub struct NposSolution24::< - VoterIndex = u32, - TargetIndex = u16, - Accuracy = sp_runtime::PerU16, - MaxVoters = ConstU32::<12500> - >(24) - ); - - #[derive(Debug)] - pub struct MinerConfig; - impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig { - type AccountId = AccountId; - type MaxLength = static_types::MaxLength; - type MaxWeight = static_types::MaxWeight; - type MaxVotesPerVoter = static_types::MaxVotesPerVoter; - type Solution = NposSolution24; - - // SYNC - fn solution_weight( - voters: u32, - targets: u32, - active_voters: u32, - desired_targets: u32, - ) -> Weight { - // Mock a RawSolution to get the correct weight without having to do the heavy work. - let raw = RawSolution { - solution: NposSolution24 { - votes1: mock_votes( - active_voters, - desired_targets.try_into().expect("Desired targets < u16::MAX"), - ), - ..Default::default() - }, - ..Default::default() - }; - - assert_eq!(raw.solution.voter_count(), active_voters as usize); - assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); - - get_weight(raw, SolutionOrSnapshotSize { voters, targets }) - } - } -} - -/// Helper to fetch the weight from a remote node -/// -/// Panics: if the RPC call fails or if decoding the response as a `Weight` fails. -fn get_weight( - raw_solution: RawSolution, - witness: SolutionOrSnapshotSize, -) -> Weight { - let tx = - unsigned_solution_tx(raw_solution, witness).expect("Failed to create dynamic transaction"); - - futures::executor::block_on(async { - let client = SHARED_CLIENT.get().expect("shared client is configured as start; qed"); - - let call_data = { - let mut buffer = Vec::new(); - - let encoded_call = client.tx().call_data(&tx).unwrap(); - let encoded_len = encoded_call.len() as u32; - - buffer.extend(encoded_call); - encoded_len.encode_to(&mut buffer); - - Bytes(buffer) - }; - - let bytes: Bytes = client - .rpc() - .request( - "state_call", - rpc_params!["TransactionPaymentCallApi_query_call_info", call_data], - ) - .await - .unwrap(); - - let info: RuntimeDispatchInfo = Decode::decode(&mut bytes.0.as_ref()).unwrap(); - - log::trace!( - target: LOG_TARGET, - "Received weight of `Solution Extrinsic` from remote node: {:?}", - info.weight - ); - - info.weight - }) -} - -fn mock_votes(voters: u32, desired_targets: u16) -> Vec<(u32, u16)> { - assert!(voters >= desired_targets as u32); - (0..voters).zip((0..desired_targets).cycle()).collect() -} - -#[cfg(test)] -#[test] -fn mock_votes_works() { - assert_eq!(mock_votes(3, 2), vec![(0, 0), (1, 1), (2, 0)]); - assert_eq!(mock_votes(3, 3), vec![(0, 0), (1, 1), (2, 2)]); -} diff --git a/src/dry_run.rs b/src/dry_run.rs index a9d57c82e..baf32147a 100644 --- a/src/dry_run.rs +++ b/src/dry_run.rs @@ -19,12 +19,8 @@ use pallet_election_provider_multi_phase::RawSolution; use crate::{ - error::Error, - helpers::{mine_solution, signed_solution_tx}, - opt::DryRunConfig, - prelude::*, - signer::Signer, - static_types, + epm_dynamic, error::Error, helpers::mine_solution, opt::DryRunConfig, prelude::*, + signer::Signer, static_types, }; use codec::Encode; @@ -65,7 +61,7 @@ where raw_solution.encode().len(), ); - let tx = signed_solution_tx(raw_solution)?; + let tx = epm_dynamic::signed_solution(raw_solution)?; let xt = api.tx().create_signed(&tx, &*signer, ExtrinsicParams::default()).await?; let outcome = api.rpc().dry_run(xt.encoded(), config.at).await?; diff --git a/src/error.rs b/src/error.rs index 394b411d4..9ff1359ab 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,8 @@ pub enum Error { Subxt(#[from] subxt::Error), #[error("Crypto error: `{0:?}`")] Crypto(sp_core::crypto::SecretStringError), + #[error("Codec error: `{0}`")] + Codec(#[from] codec::Error), #[error("Incorrect phase")] IncorrectPhase, #[error("Submission is already submitted")] diff --git a/src/helpers.rs b/src/helpers.rs index 4f8386195..9cfbb5eae 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,12 +1,9 @@ use crate::{opt::Solver, prelude::*, static_types}; -use codec::Encode; -use frame_election_provider_support::{NposSolution, PhragMMS, SequentialPhragmen}; -use frame_support::{weights::Weight, BoundedVec}; -use pallet_election_provider_multi_phase::{RawSolution, SolutionOf, SolutionOrSnapshotSize}; +use frame_election_provider_support::{PhragMMS, SequentialPhragmen}; +use frame_support::BoundedVec; +use pallet_election_provider_multi_phase::{SolutionOf, SolutionOrSnapshotSize}; use pin_project_lite::pin_project; use runtime::runtime_types::pallet_election_provider_multi_phase::RoundSnapshot; -use scale_info::{PortableRegistry, TypeInfo}; -use scale_value::scale::{decode_as_type, DecodeError, TypeId}; use sp_npos_elections::ElectionScore; use std::{ future::Future, @@ -14,11 +11,6 @@ use std::{ task::{Context, Poll}, time::{Duration, Instant}, }; -use subxt::{dynamic::Value, tx::DynamicTxPayload}; - -pub type BoundedVoters = - Vec<(AccountId, VoteWeight, BoundedVec)>; -pub type Snapshot = (BoundedVoters, Vec, u32); pin_project! { pub struct Timed @@ -131,124 +123,14 @@ where Ok((voters, targets, desired_targets)) } -#[derive(Copy, Clone, Debug)] -struct EpmConstant { - epm: &'static str, - constant: &'static str, +pub fn mock_votes(voters: u32, desired_targets: u16) -> Vec<(u32, u16)> { + assert!(voters >= desired_targets as u32); + (0..voters).zip((0..desired_targets).cycle()).collect() } -impl EpmConstant { - const fn new(constant: &'static str) -> Self { - Self { epm: "ElectionProviderMultiPhase", constant } - } - - const fn to_parts(&self) -> (&'static str, &'static str) { - (self.epm, self.constant) - } - - fn to_string(&self) -> String { - format!("{}::{}", self.epm, self.constant) - } -} - -pub(crate) async fn read_metadata_constants(api: &SubxtClient) -> Result<(), Error> { - const SIGNED_MAX_WEIGHT: EpmConstant = EpmConstant::new("SignedMaxWeight"); - const MAX_LENGTH: EpmConstant = EpmConstant::new("MinerMaxLength"); - const MAX_VOTES_PER_VOTER: EpmConstant = EpmConstant::new("MinerMaxVotesPerVoter"); - - let max_weight = read_constant::(api, SIGNED_MAX_WEIGHT)?; - let max_length: u32 = read_constant(api, MAX_LENGTH)?; - let max_votes_per_voter: u32 = read_constant(api, MAX_VOTES_PER_VOTER)?; - - log::trace!( - target: LOG_TARGET, - "updating metadata constant `{}`: {}", - SIGNED_MAX_WEIGHT.to_string(), - max_weight.ref_time() - ); - log::trace!( - target: LOG_TARGET, - "updating metadata constant `{}`: {}", - MAX_LENGTH.to_string(), - max_length - ); - log::trace!( - target: LOG_TARGET, - "updating metadata constant `{}`: {}", - MAX_VOTES_PER_VOTER.to_string(), - max_votes_per_voter - ); - - static_types::MaxWeight::set(max_weight); - static_types::MaxLength::set(max_length); - static_types::MaxVotesPerVoter::set(max_votes_per_voter); - - Ok(()) -} - -fn invalid_metadata_error(item: String, err: E) -> Error { - Error::InvalidMetadata(format!("{} failed: {}", item, err)) -} - -fn read_constant<'a, T: serde::Deserialize<'a>>( - api: &SubxtClient, - constant: EpmConstant, -) -> Result { - let (epm_name, constant) = constant.to_parts(); - - let val = api - .constants() - .at(&subxt::dynamic::constant(epm_name, constant)) - .map_err(|e| invalid_metadata_error(constant.to_string(), e))?; - - scale_value::serde::from_value::<_, T>(val).map_err(|e| { - Error::InvalidMetadata(format!("Decoding `{}` failed {}", std::any::type_name::(), e)) - }) -} - -pub fn signed_solution_tx( - solution: RawSolution, -) -> Result, Error> { - let scale_solution = to_scale_value(solution).map_err(|e| { - Error::DynamicTransaction(format!("Failed to decode `RawSolution`: {:?}", e)) - })?; - - Ok(subxt::dynamic::tx("ElectionProviderMultiPhase", "submit", vec![scale_solution])) -} - -pub fn unsigned_solution_tx( - solution: RawSolution, - witness: SolutionOrSnapshotSize, -) -> Result, Error> { - let scale_solution = to_scale_value(solution).map_err(|e| { - Error::DynamicTransaction(format!("Failed to decode `RawSolution`: {:?}", e)) - })?; - let scale_witness = to_scale_value(witness).map_err(|e| { - Error::DynamicTransaction(format!("Failed to decode `SolutionOrSnapshotSize`: {:?}", e)) - })?; - - Ok(subxt::dynamic::tx( - "ElectionProviderMultiPhase", - "submit_unsigned", - vec![scale_solution, scale_witness], - )) -} - -fn make_type() -> (TypeId, PortableRegistry) { - let m = scale_info::MetaType::new::(); - let mut types = scale_info::Registry::new(); - let id = types.register_type(&m); - let portable_registry: PortableRegistry = types.into(); - - (id.into(), portable_registry) -} - -fn to_scale_value( - val: T, -) -> Result { - let (ty_id, types) = make_type::(); - - let bytes = val.encode(); - - decode_as_type(&mut bytes.as_ref(), ty_id, &types).map(|v| v.remove_context()) +#[cfg(test)] +#[test] +fn mock_votes_works() { + assert_eq!(mock_votes(3, 2), vec![(0, 0), (1, 1), (2, 0)]); + assert_eq!(mock_votes(3, 3), vec![(0, 0), (1, 1), (2, 2)]); } diff --git a/src/lib.rs b/src/lib.rs index e2879b5f4..09e6eb9c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![allow(dead_code)] -pub mod chain; pub mod dry_run; pub mod emergency_solution; +pub mod epm_dynamic; pub mod error; pub mod helpers; pub mod monitor; diff --git a/src/main.rs b/src/main.rs index 8a51063b7..e48b0cbf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,9 +28,9 @@ //! development. It is intended to run this bot with a `restart = true` way, so that it reports it //! crash, but resumes work thereafter. -mod chain; mod dry_run; mod emergency_solution; +mod epm_dynamic; mod error; mod helpers; mod monitor; @@ -80,9 +80,9 @@ async fn main() -> Result<(), Error> { let _prometheus_handle = prometheus::run(prometheus_port.unwrap_or(DEFAULT_PROMETHEUS_PORT)) .map_err(|e| log::warn!("Failed to start prometheus endpoint: {}", e)); log::info!(target: LOG_TARGET, "Connected to chain: {}", chain); - helpers::read_metadata_constants(&api).await?; + epm_dynamic::update_metadata_constants(&api).await?; - chain::SHARED_CLIENT.set(api.clone()).expect("shared client only set once; qed"); + SHARED_CLIENT.set(api.clone()).expect("shared client only set once; qed"); // Start a new tokio task to perform the runtime updates in the background. // if this fails then the miner will be stopped and has to be re-started. @@ -181,7 +181,7 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { let version = update.runtime_version().spec_version; match updater.apply_update(update) { Ok(()) => { - if let Err(e) = helpers::read_metadata_constants(&api).await { + if let Err(e) = epm_dynamic::update_metadata_constants(&api).await { let _ = tx.send(e.into()); return } diff --git a/src/monitor.rs b/src/monitor.rs index 0822e98b9..d1d71df59 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,13 +1,14 @@ use crate::{ + epm_dynamic, error::Error, - helpers::{self, signed_solution_tx, TimedFuture}, + helpers::{self, TimedFuture}, opt::{Listen, MonitorConfig, SubmissionStrategy}, prelude::*, prometheus, signer::Signer, static_types, }; -use codec::Encode; +use codec::{Decode, Encode}; use frame_election_provider_support::NposSolution; use pallet_election_provider_multi_phase::{RawSolution, SolutionOf}; use sp_runtime::Perbill; @@ -156,7 +157,9 @@ async fn mine_and_submit_solution( let _lock = submit_lock.lock().await; - if let Err(e) = ensure_no_previous_solution(&api, hash, signer.account_id()).await { + if let Err(e) = + ensure_no_previous_solution::(&api, hash, signer.account_id()).await + { log::debug!( target: LOG_TARGET, "ensure_no_previous_solution failed: {:?}; skipping block: {}", @@ -288,18 +291,19 @@ async fn ensure_signed_phase(api: &SubxtClient, hash: Hash) -> Result<(), Error> } /// Ensure that our current `us` have not submitted anything previously. -async fn ensure_no_previous_solution( +async fn ensure_no_previous_solution( api: &SubxtClient, at: Hash, us: &AccountId, -) -> Result<(), Error> { +) -> Result<(), Error> +where + T: NposSolution + scale_info::TypeInfo + Decode + 'static, +{ let addr = runtime::storage().election_provider_multi_phase().signed_submission_indices(); let indices = api.storage().fetch_or_default(&addr, Some(at)).await?; for (_score, idx) in indices.0 { - let addr = runtime::storage().election_provider_multi_phase().signed_submissions_map(&idx); - - let submission = api.storage().fetch(&addr, Some(at)).await?; + let submission = epm_dynamic::signed_submission_at::(idx, at, api).await?; if let Some(submission) = submission { if &submission.who == us { @@ -344,7 +348,7 @@ async fn submit_and_watch_solution( (solution, score, round): (SolutionOf, sp_npos_elections::ElectionScore, u32), hash: Hash, ) -> Result<(), Error> { - let tx = signed_solution_tx(RawSolution { solution, score, round })?; + let tx = epm_dynamic::signed_solution(RawSolution { solution, score, round })?; let mut status_sub = api.tx().sign_and_submit_then_watch_default(&tx, &*signer).await.map_err(|e| { diff --git a/src/opt.rs b/src/opt.rs index 575534302..7b7007610 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -35,17 +35,17 @@ macro_rules! any_runtime { match $chain { Chain::Polkadot => { #[allow(unused)] - use $crate::chain::polkadot::MinerConfig; + use $crate::static_types::polkadot::MinerConfig; $($code)* }, Chain::Kusama => { #[allow(unused)] - use $crate::chain::kusama::MinerConfig; + use $crate::static_types::kusama::MinerConfig; $($code)* }, Chain::Westend => { #[allow(unused)] - use $crate::chain::westend::MinerConfig; + use $crate::static_types::westend::MinerConfig; $($code)* }, } diff --git a/src/prelude.rs b/src/prelude.rs index cade05d6a..d1e719199 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,6 +25,9 @@ pub use crate::{error::Error, opt::*}; pub use frame_election_provider_support::VoteWeight; pub use pallet_election_provider_multi_phase::{Miner, MinerConfig}; +use crate::static_types; +use once_cell::sync::OnceCell; + /// The account id type. pub type AccountId = subxt::ext::sp_core::crypto::AccountId32; /// The header type. We re-export it here, but we can easily get it from block as well. @@ -64,6 +67,15 @@ pub type SubxtClient = subxt::OnlineClient; /// Config used by the staking-miner pub type Config = subxt::PolkadotConfig; +pub type BoundedVoters = Vec<( + AccountId, + VoteWeight, + frame_support::BoundedVec, +)>; +pub type Snapshot = (BoundedVoters, Vec, u32); +pub type SignedSubmission = + pallet_election_provider_multi_phase::SignedSubmission; + #[subxt::subxt( runtime_metadata_path = "artifacts/metadata.scale", derive_for_all_types = "Clone, Debug, Eq, PartialEq", @@ -88,3 +100,5 @@ pub mod runtime { #[subxt(substitute_type = "pallet_election_provider_multi_phase::SolutionOrSnapshotSize")] use ::pallet_election_provider_multi_phase::SolutionOrSnapshotSize; } + +pub static SHARED_CLIENT: OnceCell = OnceCell::new(); diff --git a/src/static_types.rs b/src/static_types.rs index 8f4f8b45f..65888599c 100644 --- a/src/static_types.rs +++ b/src/static_types.rs @@ -1,3 +1,8 @@ +use crate::{epm_dynamic, helpers::mock_votes, prelude::*}; +use frame_election_provider_support::traits::NposSolution; +use frame_support::{traits::ConstU32, weights::Weight}; +use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; + macro_rules! impl_atomic_u32_parameter_types { ($mod:ident, $name:ident) => { mod $mod { @@ -59,3 +64,161 @@ mod max_weight { impl_atomic_u32_parameter_types!(max_length, MaxLength); impl_atomic_u32_parameter_types!(max_votes, MaxVotesPerVoter); pub use max_weight::MaxWeight; + +pub mod westend { + use super::*; + + // SYNC + frame_election_provider_support::generate_solution_type!( + #[compact] + pub struct NposSolution16::< + VoterIndex = u32, + TargetIndex = u16, + Accuracy = sp_runtime::PerU16, + MaxVoters = ConstU32::<22500> + >(16) + ); + + #[derive(Debug)] + pub struct MinerConfig; + impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig { + type AccountId = AccountId; + type MaxLength = MaxLength; + type MaxWeight = MaxWeight; + type MaxVotesPerVoter = MaxVotesPerVoter; + type Solution = NposSolution16; + + fn solution_weight( + voters: u32, + targets: u32, + active_voters: u32, + desired_targets: u32, + ) -> Weight { + // Mock a RawSolution to get the correct weight without having to do the heavy work. + let raw_solution = RawSolution { + solution: NposSolution16 { + votes1: mock_votes( + active_voters, + desired_targets.try_into().expect("Desired targets < u16::MAX"), + ), + ..Default::default() + }, + ..Default::default() + }; + + assert_eq!(raw_solution.solution.voter_count(), active_voters as usize); + assert_eq!(raw_solution.solution.unique_targets().len(), desired_targets as usize); + + epm_dynamic::runtime_api_solution_weight( + raw_solution, + SolutionOrSnapshotSize { voters, targets }, + ) + } + } +} + +pub mod polkadot { + use super::*; + use frame_support::traits::ConstU32; + use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; + + // SYNC + frame_election_provider_support::generate_solution_type!( + #[compact] + pub struct NposSolution16::< + VoterIndex = u32, + TargetIndex = u16, + Accuracy = sp_runtime::PerU16, + MaxVoters = ConstU32::<22500> + >(16) + ); + + #[derive(Debug)] + pub struct MinerConfig; + impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig { + type AccountId = AccountId; + type MaxLength = MaxLength; + type MaxWeight = MaxWeight; + type MaxVotesPerVoter = MaxVotesPerVoter; + type Solution = NposSolution16; + + fn solution_weight( + voters: u32, + targets: u32, + active_voters: u32, + desired_targets: u32, + ) -> Weight { + // Mock a RawSolution to get the correct weight without having to do the heavy work. + let raw = RawSolution { + solution: NposSolution16 { + votes1: mock_votes( + active_voters, + desired_targets.try_into().expect("Desired targets < u16::MAX"), + ), + ..Default::default() + }, + ..Default::default() + }; + + assert_eq!(raw.solution.voter_count(), active_voters as usize); + assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); + + epm_dynamic::runtime_api_solution_weight( + raw, + SolutionOrSnapshotSize { voters, targets }, + ) + } + } +} + +pub mod kusama { + use super::*; + + // SYNC + frame_election_provider_support::generate_solution_type!( + #[compact] + pub struct NposSolution24::< + VoterIndex = u32, + TargetIndex = u16, + Accuracy = sp_runtime::PerU16, + MaxVoters = ConstU32::<12500> + >(24) + ); + + #[derive(Debug)] + pub struct MinerConfig; + impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig { + type AccountId = AccountId; + type MaxLength = MaxLength; + type MaxWeight = MaxWeight; + type MaxVotesPerVoter = MaxVotesPerVoter; + type Solution = NposSolution24; + + fn solution_weight( + voters: u32, + targets: u32, + active_voters: u32, + desired_targets: u32, + ) -> Weight { + // Mock a RawSolution to get the correct weight without having to do the heavy work. + let raw = RawSolution { + solution: NposSolution24 { + votes1: mock_votes( + active_voters, + desired_targets.try_into().expect("Desired targets < u16::MAX"), + ), + ..Default::default() + }, + ..Default::default() + }; + + assert_eq!(raw.solution.voter_count(), active_voters as usize); + assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); + + epm_dynamic::runtime_api_solution_weight( + raw, + SolutionOrSnapshotSize { voters, targets }, + ) + } + } +} From 41cb6792c6ad25f97862966ad8f0cd797ca06861 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 30 Sep 2022 17:29:57 +0200 Subject: [PATCH 15/18] commit missing file --- src/epm_dynamic.rs | 213 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 src/epm_dynamic.rs diff --git a/src/epm_dynamic.rs b/src/epm_dynamic.rs new file mode 100644 index 000000000..c601c62a2 --- /dev/null +++ b/src/epm_dynamic.rs @@ -0,0 +1,213 @@ +use crate::{prelude::*, static_types}; +use codec::{Decode, Encode}; +use frame_election_provider_support::NposSolution; +use frame_support::weights::Weight; +use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; +use pallet_transaction_payment::RuntimeDispatchInfo; +use scale_info::{PortableRegistry, TypeInfo}; +use scale_value::scale::{decode_as_type, encode_as_type, TypeId}; +use sp_core::Bytes; +use subxt::{dynamic::Value, rpc::rpc_params, tx::DynamicTxPayload}; + +const EPM_PALLET_NAME: &str = "ElectionProviderMultiPhase"; + +#[derive(Copy, Clone, Debug)] +struct EpmConstant { + epm: &'static str, + constant: &'static str, +} + +impl EpmConstant { + const fn new(constant: &'static str) -> Self { + Self { epm: EPM_PALLET_NAME, constant } + } + + const fn to_parts(&self) -> (&'static str, &'static str) { + (self.epm, self.constant) + } + + fn to_string(&self) -> String { + format!("{}::{}", self.epm, self.constant) + } +} + +pub(crate) async fn update_metadata_constants(api: &SubxtClient) -> Result<(), Error> { + const SIGNED_MAX_WEIGHT: EpmConstant = EpmConstant::new("SignedMaxWeight"); + const MAX_LENGTH: EpmConstant = EpmConstant::new("MinerMaxLength"); + const MAX_VOTES_PER_VOTER: EpmConstant = EpmConstant::new("MinerMaxVotesPerVoter"); + + let max_weight = read_constant::(api, SIGNED_MAX_WEIGHT)?; + let max_length: u32 = read_constant(api, MAX_LENGTH)?; + let max_votes_per_voter: u32 = read_constant(api, MAX_VOTES_PER_VOTER)?; + + log::trace!( + target: LOG_TARGET, + "updating metadata constant `{}`: {}", + SIGNED_MAX_WEIGHT.to_string(), + max_weight.ref_time() + ); + log::trace!( + target: LOG_TARGET, + "updating metadata constant `{}`: {}", + MAX_LENGTH.to_string(), + max_length + ); + log::trace!( + target: LOG_TARGET, + "updating metadata constant `{}`: {}", + MAX_VOTES_PER_VOTER.to_string(), + max_votes_per_voter + ); + + static_types::MaxWeight::set(max_weight); + static_types::MaxLength::set(max_length); + static_types::MaxVotesPerVoter::set(max_votes_per_voter); + + Ok(()) +} + +fn invalid_metadata_error(item: String, err: E) -> Error { + Error::InvalidMetadata(format!("{} failed: {}", item, err)) +} + +fn read_constant<'a, T: serde::Deserialize<'a>>( + api: &SubxtClient, + constant: EpmConstant, +) -> Result { + let (epm_name, constant) = constant.to_parts(); + + let val = api + .constants() + .at(&subxt::dynamic::constant(epm_name, constant)) + .map_err(|e| invalid_metadata_error(constant.to_string(), e))?; + + scale_value::serde::from_value::<_, T>(val).map_err(|e| { + Error::InvalidMetadata(format!("Decoding `{}` failed {}", std::any::type_name::(), e)) + }) +} + +pub fn signed_solution( + solution: RawSolution, +) -> Result, Error> { + let scale_solution = to_scale_value(solution).map_err(|e| { + Error::DynamicTransaction(format!("Failed to decode `RawSolution`: {:?}", e)) + })?; + + Ok(subxt::dynamic::tx(EPM_PALLET_NAME, "submit", vec![scale_solution])) +} + +pub fn unsigned_solution( + solution: RawSolution, + witness: SolutionOrSnapshotSize, +) -> Result, Error> { + let scale_solution = to_scale_value(solution)?; + let scale_witness = to_scale_value(witness)?; + + Ok(subxt::dynamic::tx(EPM_PALLET_NAME, "submit_unsigned", vec![scale_solution, scale_witness])) +} + +pub async fn signed_submission_at( + idx: u32, + at: Hash, + api: &SubxtClient, +) -> Result>, Error> { + let scale_idx = Value::u128(idx as u128); + let addr = subxt::dynamic::storage(EPM_PALLET_NAME, "SignedSubmissionsMap", vec![scale_idx]); + + match api.storage().fetch(&addr, Some(at)).await { + Ok(Some(val)) => { + let val = val.remove_context(); + let v = encode_scale_value::>(&val)?; + let submissions = Decode::decode(&mut v.as_ref())?; + Ok(Some(submissions)) + }, + Ok(None) => Ok(None), + Err(err) => Err(err.into()), + } +} + +fn make_type() -> (TypeId, PortableRegistry) { + let m = scale_info::MetaType::new::(); + let mut types = scale_info::Registry::new(); + let id = types.register_type(&m); + let portable_registry: PortableRegistry = types.into(); + + (id.into(), portable_registry) +} + +fn to_scale_value(val: T) -> Result { + let (ty_id, types) = make_type::(); + + let bytes = val.encode(); + + decode_as_type(&mut bytes.as_ref(), ty_id, &types) + .map(|v| v.remove_context()) + .map_err(|e| { + Error::DynamicTransaction(format!( + "Failed to decode {}: {:?}", + std::any::type_name::(), + e + )) + }) +} + +fn encode_scale_value(val: &Value) -> Result, Error> { + let (ty_id, types) = make_type::(); + + let mut bytes = Vec::new(); + + encode_as_type(val, ty_id, &types, &mut bytes).map_err(|e| { + Error::DynamicTransaction(format!( + "Failed to encode {}: {:?}", + std::any::type_name::(), + e + )) + })?; + Ok(bytes) +} + +/// Fetch the weight for `RawSolution` from a remote node +/// +/// Panics: if the RPC call fails or if decoding the response as a `Weight` fails. +pub fn runtime_api_solution_weight( + raw_solution: RawSolution, + witness: SolutionOrSnapshotSize, +) -> Weight { + let tx = + unsigned_solution(raw_solution, witness).expect("Failed to create dynamic transaction"); + + futures::executor::block_on(async { + let client = SHARED_CLIENT.get().expect("shared client is configured as start; qed"); + + let call_data = { + let mut buffer = Vec::new(); + + let encoded_call = client.tx().call_data(&tx).unwrap(); + let encoded_len = encoded_call.len() as u32; + + buffer.extend(encoded_call); + encoded_len.encode_to(&mut buffer); + + Bytes(buffer) + }; + + let bytes: Bytes = client + .rpc() + .request( + "state_call", + rpc_params!["TransactionPaymentCallApi_query_call_info", call_data], + ) + .await + .unwrap(); + + let info: RuntimeDispatchInfo = Decode::decode(&mut bytes.0.as_ref()).unwrap(); + + log::trace!( + target: LOG_TARGET, + "Received weight of `Solution Extrinsic` from remote node: {:?}", + info.weight + ); + + info.weight + }) +} From 9332b65782166e43653e3ed4426087f003e9adc0 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 30 Sep 2022 17:55:20 +0200 Subject: [PATCH 16/18] fix test build --- tests/monitor.rs | 53 +++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/tests/monitor.rs b/tests/monitor.rs index 0e5f2b104..34b0ab8c8 100644 --- a/tests/monitor.rs +++ b/tests/monitor.rs @@ -9,9 +9,8 @@ use common::{init_logger, run_polkadot_node, KillChildOnDrop}; use pallet_election_provider_multi_phase::ReadySolution; use sp_storage::StorageChangeSet; use staking_miner::{ - any_runtime, opt::Chain, - prelude::{AccountId, Hash, SubxtClient}, + prelude::{runtime, AccountId, Hash, SubxtClient}, }; use std::{ process, @@ -42,38 +41,36 @@ async fn test_submit_solution(chain: Chain) { .unwrap(), ); - any_runtime!(chain, { - let api = SubxtClient::from_url(&ws_url).await.unwrap(); + let api = SubxtClient::from_url(&ws_url).await.unwrap(); - let now = Instant::now(); + let now = Instant::now(); - let key = Bytes( - runtime::storage() - .election_provider_multi_phase() - .queued_solution() - .to_root_bytes(), - ); + let key = Bytes( + runtime::storage() + .election_provider_multi_phase() + .queued_solution() + .to_root_bytes(), + ); - let mut sub = api - .rpc() - .subscribe("state_subscribeStorage", rpc_params![vec![key]], "state_unsubscribeStorage") - .await - .unwrap(); + let mut sub = api + .rpc() + .subscribe("state_subscribeStorage", rpc_params![vec![key]], "state_unsubscribeStorage") + .await + .unwrap(); - let mut success = false; + let mut success = false; - while now.elapsed() < MAX_DURATION_FOR_SUBMIT_SOLUTION { - let x: StorageChangeSet = sub.next().await.unwrap().unwrap(); + while now.elapsed() < MAX_DURATION_FOR_SUBMIT_SOLUTION { + let x: StorageChangeSet = sub.next().await.unwrap().unwrap(); - if let Some(data) = x.changes[0].clone().1 { - let solution: ReadySolution = Decode::decode(&mut data.0.as_slice()) - .expect("Failed to decode storage as QueuedSolution"); - println!("solution: {:?}", solution); - success = true; - break - } + if let Some(data) = x.changes[0].clone().1 { + let solution: ReadySolution = Decode::decode(&mut data.0.as_slice()) + .expect("Failed to decode storage as QueuedSolution"); + println!("solution: {:?}", solution); + success = true; + break } + } - assert!(success); - }); + assert!(success); } From 2e4b52f89369ed40724af0775e67a5f4c4d0fa3b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 5 Oct 2022 19:11:57 +0200 Subject: [PATCH 17/18] cleanup --- README.md | 19 +++- src/dry_run.rs | 12 +-- src/emergency_solution.rs | 2 +- src/{epm_dynamic.rs => epm.rs} | 161 +++++++++++++++++++++++++-------- src/error.rs | 16 ++++ src/helpers.rs | 106 ++++------------------ src/lib.rs | 18 +++- src/main.rs | 8 +- src/monitor.rs | 49 +++++++--- src/opt.rs | 16 ++++ src/prelude.rs | 8 +- src/signer.rs | 2 +- src/static_types.rs | 47 +++++++--- 13 files changed, 287 insertions(+), 177 deletions(-) rename src/{epm_dynamic.rs => epm.rs} (52%) diff --git a/README.md b/README.md index a7c227f68..7d6af7ea9 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,32 @@ WARNING this library is under active development DO NOT USE IN PRODUCTION. The library is a re-write of [polkadot staking miner](https://github.com/paritytech/polkadot/tree/master/utils/staking-miner) using [subxt](https://github.com/paritytech/subxt) -to avoid hard dependency to each runtime version. +to avoid hard dependency to each runtime version. It's using a static metadata (metadata.scale) file to generate rust types, instructions how to update metadata can be found [here](#update-metadata) The intention is that this library will "only break" once the [pallet-election-provider-multi-phase](https://crates.parity.io/pallet_election_provider_multi_phase/index.html) breaks i.e, not on every runtime upgrade. + + You can check the help with: ``` staking-miner --help ``` +## Update metadata + +The static metadata file is stored at [`artifacts/metadata.scale`] +To update the metadata you need to connect to a polkadot, kusama or westend node + +```bash +# Install subxt-cli +$ cargo install --locked subxt-cli +# Download metadata from local node and replace the current metadata +# See `https://github.com/paritytech/subxt/tree/master/cli` for further documentation of the `subxt-cli` tool. +$ subxt metadata -f bytes > artifacts/metadata.scale +# Inspect the generated code +$ subxt codegen --file artifacts/metadata.scale | rustfmt +nightly > code.rs +``` + ## Building diff --git a/src/dry_run.rs b/src/dry_run.rs index baf32147a..4c4ff43d4 100644 --- a/src/dry_run.rs +++ b/src/dry_run.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2021-2022 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -18,10 +18,7 @@ use pallet_election_provider_multi_phase::RawSolution; -use crate::{ - epm_dynamic, error::Error, helpers::mine_solution, opt::DryRunConfig, prelude::*, - signer::Signer, static_types, -}; +use crate::{epm, error::Error, opt::DryRunConfig, prelude::*, signer::Signer, static_types}; use codec::Encode; pub async fn dry_run_cmd(api: SubxtClient, config: DryRunConfig) -> Result<(), Error> @@ -42,7 +39,8 @@ where log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer, account_info); - let (solution, score, _size) = mine_solution::(&api, config.at, config.solver).await?; + let (solution, score, _size) = + epm::fetch_snapshot_and_mine_solution::(&api, config.at, config.solver).await?; let round = api .storage() @@ -61,7 +59,7 @@ where raw_solution.encode().len(), ); - let tx = epm_dynamic::signed_solution(raw_solution)?; + let tx = epm::signed_solution(raw_solution)?; let xt = api.tx().create_signed(&tx, &*signer, ExtrinsicParams::default()).await?; let outcome = api.rpc().dry_run(xt.encoded(), config.at).await?; diff --git a/src/emergency_solution.rs b/src/emergency_solution.rs index 8ad251503..775b1c60b 100644 --- a/src/emergency_solution.rs +++ b/src/emergency_solution.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2021-2022 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify diff --git a/src/epm_dynamic.rs b/src/epm.rs similarity index 52% rename from src/epm_dynamic.rs rename to src/epm.rs index c601c62a2..9f1b5854b 100644 --- a/src/epm_dynamic.rs +++ b/src/epm.rs @@ -1,12 +1,32 @@ -use crate::{prelude::*, static_types}; +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Wrappers or helpers for [`pallet_election_provider_multi_phase`]. + +use crate::{opt::Solver, prelude::*, static_types}; use codec::{Decode, Encode}; -use frame_election_provider_support::NposSolution; -use frame_support::weights::Weight; -use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; +use frame_election_provider_support::{NposSolution, PhragMMS, SequentialPhragmen}; +use frame_support::{weights::Weight, BoundedVec}; +use pallet_election_provider_multi_phase::{RawSolution, SolutionOf, SolutionOrSnapshotSize}; use pallet_transaction_payment::RuntimeDispatchInfo; +use runtime::runtime_types::pallet_election_provider_multi_phase::RoundSnapshot; use scale_info::{PortableRegistry, TypeInfo}; use scale_value::scale::{decode_as_type, encode_as_type, TypeId}; use sp_core::Bytes; +use sp_npos_elections::ElectionScore; use subxt::{dynamic::Value, rpc::rpc_params, tx::DynamicTxPayload}; const EPM_PALLET_NAME: &str = "ElectionProviderMultiPhase"; @@ -31,6 +51,7 @@ impl EpmConstant { } } +/// Read the constants from the metadata and updates the static types. pub(crate) async fn update_metadata_constants(api: &SubxtClient) -> Result<(), Error> { const SIGNED_MAX_WEIGHT: EpmConstant = EpmConstant::new("SignedMaxWeight"); const MAX_LENGTH: EpmConstant = EpmConstant::new("MinerMaxLength"); @@ -86,6 +107,7 @@ fn read_constant<'a, T: serde::Deserialize<'a>>( }) } +/// Helper to construct a signed solution transaction. pub fn signed_solution( solution: RawSolution, ) -> Result, Error> { @@ -96,6 +118,7 @@ pub fn signed_solution( Ok(subxt::dynamic::tx(EPM_PALLET_NAME, "submit", vec![scale_solution])) } +/// Helper to construct a unsigned solution transaction. pub fn unsigned_solution( solution: RawSolution, witness: SolutionOrSnapshotSize, @@ -106,6 +129,7 @@ pub fn unsigned_solution( Ok(subxt::dynamic::tx(EPM_PALLET_NAME, "submit_unsigned", vec![scale_solution, scale_witness])) } +/// Helper to the signed submissions at the current block. pub async fn signed_submission_at( idx: u32, at: Hash, @@ -126,6 +150,67 @@ pub async fn signed_submission_at } } +/// Helper to fetch snapshot data via RPC +/// and compute an NPos solution via [`pallet_election_provider_multi_phase`]. +pub async fn fetch_snapshot_and_mine_solution( + api: &SubxtClient, + hash: Option, + solver: Solver, +) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), Error> +where + T: MinerConfig + + Send + + Sync + + 'static, + T::Solution: Send, +{ + let RoundSnapshot { voters, targets } = api + .storage() + .fetch(&runtime::storage().election_provider_multi_phase().snapshot(), hash) + .await? + .unwrap_or_default(); + + let desired_targets = api + .storage() + .fetch(&runtime::storage().election_provider_multi_phase().desired_targets(), hash) + .await? + .unwrap_or_default(); + + let voters: Vec<_> = voters + .into_iter() + .map(|(a, b, mut c)| { + let mut bounded_vec: BoundedVec = BoundedVec::default(); + // If this fails just crash the task. + bounded_vec.try_append(&mut c.0).unwrap_or_else(|_| panic!("BoundedVec capacity: {} failed; `MinerConfig::MaxVotesPerVoter` is different from the chain data; this is a bug please file an issue", static_types::MaxVotesPerVoter::get())); + (a, b, bounded_vec) + }) + .collect(); + + let blocking_task = tokio::task::spawn_blocking(move || match solver { + Solver::SeqPhragmen { iterations } => { + BalanceIterations::set(iterations); + Miner::::mine_solution_with_snapshot::< + SequentialPhragmen, + >(voters, targets, desired_targets) + }, + Solver::PhragMMS { iterations } => { + BalanceIterations::set(iterations); + Miner::::mine_solution_with_snapshot::>( + voters, + targets, + desired_targets, + ) + }, + }) + .await; + + match blocking_task { + Ok(Ok(res)) => Ok(res), + Ok(Err(err)) => Err(Error::Other(format!("{:?}", err))), + Err(err) => Err(Error::Other(format!("{:?}", err))), + } +} + fn make_type() -> (TypeId, PortableRegistry) { let m = scale_info::MetaType::new::(); let mut types = scale_info::Registry::new(); @@ -167,47 +252,51 @@ fn encode_scale_value(val: &Value) -> Result, Err } /// Fetch the weight for `RawSolution` from a remote node -/// -/// Panics: if the RPC call fails or if decoding the response as a `Weight` fails. -pub fn runtime_api_solution_weight( +pub async fn runtime_api_solution_weight( raw_solution: RawSolution, witness: SolutionOrSnapshotSize, -) -> Weight { - let tx = - unsigned_solution(raw_solution, witness).expect("Failed to create dynamic transaction"); +) -> Result { + let tx = unsigned_solution(raw_solution, witness)?; - futures::executor::block_on(async { - let client = SHARED_CLIENT.get().expect("shared client is configured as start; qed"); + let client = SHARED_CLIENT.get().expect("shared client is configured as start; qed"); - let call_data = { - let mut buffer = Vec::new(); + let call_data = { + let mut buffer = Vec::new(); - let encoded_call = client.tx().call_data(&tx).unwrap(); - let encoded_len = encoded_call.len() as u32; + let encoded_call = client.tx().call_data(&tx).unwrap(); + let encoded_len = encoded_call.len() as u32; - buffer.extend(encoded_call); - encoded_len.encode_to(&mut buffer); + buffer.extend(encoded_call); + encoded_len.encode_to(&mut buffer); - Bytes(buffer) - }; - - let bytes: Bytes = client - .rpc() - .request( - "state_call", - rpc_params!["TransactionPaymentCallApi_query_call_info", call_data], - ) - .await - .unwrap(); + Bytes(buffer) + }; - let info: RuntimeDispatchInfo = Decode::decode(&mut bytes.0.as_ref()).unwrap(); + let bytes: Bytes = client + .rpc() + .request("state_call", rpc_params!["TransactionPaymentCallApi_query_call_info", call_data]) + .await?; - log::trace!( - target: LOG_TARGET, - "Received weight of `Solution Extrinsic` from remote node: {:?}", - info.weight - ); + let info: RuntimeDispatchInfo = Decode::decode(&mut bytes.0.as_ref())?; + log::trace!( + target: LOG_TARGET, + "Received weight of `Solution Extrinsic` from remote node: {:?}", info.weight - }) + ); + + Ok(info.weight) +} + +/// Helper to mock the votes based on `voters` and `desired_targets`. +pub fn mock_votes(voters: u32, desired_targets: u16) -> Vec<(u32, u16)> { + assert!(voters >= desired_targets as u32); + (0..voters).zip((0..desired_targets).cycle()).collect() +} + +#[cfg(test)] +#[test] +fn mock_votes_works() { + assert_eq!(mock_votes(3, 2), vec![(0, 0), (1, 1), (2, 0)]); + assert_eq!(mock_votes(3, 3), vec![(0, 0), (1, 1), (2, 2)]); } diff --git a/src/error.rs b/src/error.rs index 9ff1359ab..ccb1986a7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,19 @@ +// Copyright 2021-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + use crate::prelude::sp_core; #[derive(thiserror::Error, Debug)] diff --git a/src/helpers.rs b/src/helpers.rs index 9cfbb5eae..0af2b2359 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,10 +1,20 @@ -use crate::{opt::Solver, prelude::*, static_types}; -use frame_election_provider_support::{PhragMMS, SequentialPhragmen}; -use frame_support::BoundedVec; -use pallet_election_provider_multi_phase::{SolutionOf, SolutionOrSnapshotSize}; +// Copyright 2021-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + use pin_project_lite::pin_project; -use runtime::runtime_types::pallet_election_provider_multi_phase::RoundSnapshot; -use sp_npos_elections::ElectionScore; use std::{ future::Future, pin::Pin, @@ -50,87 +60,3 @@ pub trait TimedFuture: Sized + Future { } impl TimedFuture for F {} - -pub async fn mine_solution( - api: &SubxtClient, - hash: Option, - solver: Solver, -) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), Error> -where - T: MinerConfig - + Send - + Sync - + 'static, - T::Solution: Send, -{ - let (voters, targets, desired_targets) = snapshot::(&api, hash).await?; - - let blocking_task = tokio::task::spawn_blocking(move || match solver { - Solver::SeqPhragmen { iterations } => { - BalanceIterations::set(iterations); - Miner::::mine_solution_with_snapshot::< - SequentialPhragmen, - >(voters, targets, desired_targets) - }, - Solver::PhragMMS { iterations } => { - BalanceIterations::set(iterations); - Miner::::mine_solution_with_snapshot::>( - voters, - targets, - desired_targets, - ) - }, - }) - .await; - - match blocking_task { - Ok(Ok(res)) => Ok(res), - Ok(Err(err)) => Err(Error::Other(format!("{:?}", err))), - Err(err) => Err(Error::Other(format!("{:?}", err))), - } -} - -pub async fn snapshot( - api: &SubxtClient, - hash: Option, -) -> Result -where - T: MinerConfig + Send + Sync + 'static, - T::Solution: Send, -{ - let RoundSnapshot { voters, targets } = api - .storage() - .fetch(&runtime::storage().election_provider_multi_phase().snapshot(), hash) - .await? - .unwrap_or_default(); - - let desired_targets = api - .storage() - .fetch(&runtime::storage().election_provider_multi_phase().desired_targets(), hash) - .await? - .unwrap_or_default(); - - let voters: Vec<_> = voters - .into_iter() - .map(|(a, b, mut c)| { - let mut bounded_vec: BoundedVec = BoundedVec::default(); - // If this fails just crash the task. - bounded_vec.try_append(&mut c.0).unwrap_or_else(|_| panic!("BoundedVec capacity: {} failed; `MinerConfig::MaxVotesPerVoter` is different from the chain data; this is a bug please file an issue", static_types::MaxVotesPerVoter::get())); - (a, b, bounded_vec) - }) - .collect(); - - Ok((voters, targets, desired_targets)) -} - -pub fn mock_votes(voters: u32, desired_targets: u16) -> Vec<(u32, u16)> { - assert!(voters >= desired_targets as u32); - (0..voters).zip((0..desired_targets).cycle()).collect() -} - -#[cfg(test)] -#[test] -fn mock_votes_works() { - assert_eq!(mock_votes(3, 2), vec![(0, 0), (1, 1), (2, 0)]); - assert_eq!(mock_votes(3, 3), vec![(0, 0), (1, 1), (2, 2)]); -} diff --git a/src/lib.rs b/src/lib.rs index 09e6eb9c0..3c51cd658 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,24 @@ +// Copyright 2021-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + #![allow(dead_code)] pub mod dry_run; pub mod emergency_solution; -pub mod epm_dynamic; +pub mod epm; pub mod error; pub mod helpers; pub mod monitor; diff --git a/src/main.rs b/src/main.rs index e48b0cbf5..dfcd8f400 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2021-2022 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -30,7 +30,7 @@ mod dry_run; mod emergency_solution; -mod epm_dynamic; +mod epm; mod error; mod helpers; mod monitor; @@ -80,7 +80,7 @@ async fn main() -> Result<(), Error> { let _prometheus_handle = prometheus::run(prometheus_port.unwrap_or(DEFAULT_PROMETHEUS_PORT)) .map_err(|e| log::warn!("Failed to start prometheus endpoint: {}", e)); log::info!(target: LOG_TARGET, "Connected to chain: {}", chain); - epm_dynamic::update_metadata_constants(&api).await?; + epm::update_metadata_constants(&api).await?; SHARED_CLIENT.set(api.clone()).expect("shared client only set once; qed"); @@ -181,7 +181,7 @@ async fn runtime_upgrade_task(api: SubxtClient, tx: oneshot::Sender) { let version = update.runtime_version().spec_version; match updater.apply_update(update) { Ok(()) => { - if let Err(e) = epm_dynamic::update_metadata_constants(&api).await { + if let Err(e) = epm::update_metadata_constants(&api).await { let _ = tx.send(e.into()); return } diff --git a/src/monitor.rs b/src/monitor.rs index d1d71df59..e293b508f 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,7 +1,23 @@ +// Copyright 2021-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + use crate::{ - epm_dynamic, + epm, error::Error, - helpers::{self, TimedFuture}, + helpers::TimedFuture, opt::{Listen, MonitorConfig, SubmissionStrategy}, prelude::*, prometheus, @@ -172,7 +188,10 @@ async fn mine_and_submit_solution( } let (solution, score) = - match helpers::mine_solution::(&api, Some(hash), config.solver).timed().await { + match epm::fetch_snapshot_and_mine_solution::(&api, Some(hash), config.solver) + .timed() + .await + { (Ok((solution, score, size)), elapsed) => { let elapsed_ms = elapsed.as_millis(); let encoded_len = solution.encoded_size(); @@ -186,16 +205,16 @@ async fn mine_and_submit_solution( .unwrap(); log::info!( - target: LOG_TARGET, - "Mined solution with {:?} size: {:?} round: {:?} at: {}, took: {} ms, len: {:?}, weight = {:?}", - score, - size, - round, - at.number(), - elapsed_ms, - encoded_len, - final_weight, - ); + target: LOG_TARGET, + "Mined solution with {:?} size: {:?} round: {:?} at: {}, took: {} ms, len: {:?}, weight = {:?}", + score, + size, + round, + at.number(), + elapsed_ms, + encoded_len, + final_weight, + ); prometheus::set_length(encoded_len); prometheus::set_weight(final_weight); @@ -303,7 +322,7 @@ where let indices = api.storage().fetch_or_default(&addr, Some(at)).await?; for (_score, idx) in indices.0 { - let submission = epm_dynamic::signed_submission_at::(idx, at, api).await?; + let submission = epm::signed_submission_at::(idx, at, api).await?; if let Some(submission) = submission { if &submission.who == us { @@ -348,7 +367,7 @@ async fn submit_and_watch_solution( (solution, score, round): (SolutionOf, sp_npos_elections::ElectionScore, u32), hash: Hash, ) -> Result<(), Error> { - let tx = epm_dynamic::signed_solution(RawSolution { solution, score, round })?; + let tx = epm::signed_solution(RawSolution { solution, score, round })?; let mut status_sub = api.tx().sign_and_submit_then_watch_default(&tx, &*signer).await.map_err(|e| { diff --git a/src/opt.rs b/src/opt.rs index 7b7007610..7978582d8 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -1,3 +1,19 @@ +// Copyright 2021-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + use crate::{error::Error, prelude::*}; use clap::*; diff --git a/src/prelude.rs b/src/prelude.rs index d1e719199..d80fd45e4 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,7 +25,6 @@ pub use crate::{error::Error, opt::*}; pub use frame_election_provider_support::VoteWeight; pub use pallet_election_provider_multi_phase::{Miner, MinerConfig}; -use crate::static_types; use once_cell::sync::OnceCell; /// The account id type. @@ -67,12 +66,7 @@ pub type SubxtClient = subxt::OnlineClient; /// Config used by the staking-miner pub type Config = subxt::PolkadotConfig; -pub type BoundedVoters = Vec<( - AccountId, - VoteWeight, - frame_support::BoundedVec, -)>; -pub type Snapshot = (BoundedVoters, Vec, u32); +/// Submission type used by the staking miner. pub type SignedSubmission = pallet_election_provider_multi_phase::SignedSubmission; diff --git a/src/signer.rs b/src/signer.rs index 32b767ea2..1f7ea6a1c 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2021-2022 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify diff --git a/src/static_types.rs b/src/static_types.rs index 65888599c..07690894e 100644 --- a/src/static_types.rs +++ b/src/static_types.rs @@ -1,4 +1,20 @@ -use crate::{epm_dynamic, helpers::mock_votes, prelude::*}; +// Copyright 2021-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::{epm, prelude::*}; use frame_election_provider_support::traits::NposSolution; use frame_support::{traits::ConstU32, weights::Weight}; use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; @@ -95,9 +111,9 @@ pub mod westend { desired_targets: u32, ) -> Weight { // Mock a RawSolution to get the correct weight without having to do the heavy work. - let raw_solution = RawSolution { + let raw = RawSolution { solution: NposSolution16 { - votes1: mock_votes( + votes1: epm::mock_votes( active_voters, desired_targets.try_into().expect("Desired targets < u16::MAX"), ), @@ -106,13 +122,14 @@ pub mod westend { ..Default::default() }; - assert_eq!(raw_solution.solution.voter_count(), active_voters as usize); - assert_eq!(raw_solution.solution.unique_targets().len(), desired_targets as usize); + assert_eq!(raw.solution.voter_count(), active_voters as usize); + assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); - epm_dynamic::runtime_api_solution_weight( - raw_solution, + futures::executor::block_on(epm::runtime_api_solution_weight( + raw, SolutionOrSnapshotSize { voters, targets }, - ) + )) + .expect("solution_weight should work") } } } @@ -151,7 +168,7 @@ pub mod polkadot { // Mock a RawSolution to get the correct weight without having to do the heavy work. let raw = RawSolution { solution: NposSolution16 { - votes1: mock_votes( + votes1: epm::mock_votes( active_voters, desired_targets.try_into().expect("Desired targets < u16::MAX"), ), @@ -163,10 +180,11 @@ pub mod polkadot { assert_eq!(raw.solution.voter_count(), active_voters as usize); assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); - epm_dynamic::runtime_api_solution_weight( + futures::executor::block_on(epm::runtime_api_solution_weight( raw, SolutionOrSnapshotSize { voters, targets }, - ) + )) + .expect("solution_weight should work") } } } @@ -203,7 +221,7 @@ pub mod kusama { // Mock a RawSolution to get the correct weight without having to do the heavy work. let raw = RawSolution { solution: NposSolution24 { - votes1: mock_votes( + votes1: epm::mock_votes( active_voters, desired_targets.try_into().expect("Desired targets < u16::MAX"), ), @@ -215,10 +233,11 @@ pub mod kusama { assert_eq!(raw.solution.voter_count(), active_voters as usize); assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); - epm_dynamic::runtime_api_solution_weight( + futures::executor::block_on(epm::runtime_api_solution_weight( raw, SolutionOrSnapshotSize { voters, targets }, - ) + )) + .expect("solution_weight should work") } } } From e9ffc85f55840f23c030fcae0deafb7a9f7736e5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 5 Oct 2022 19:26:01 +0200 Subject: [PATCH 18/18] update substrate --- Cargo.lock | 84 +++++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03036b1b5..4d43960d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "array-bytes" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a913633b0c922e6b745072795f50d90ebea78ba31a57e2ac8c2fc7b50950949" + [[package]] name = "arrayref" version = "0.3.6" @@ -759,7 +765,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "frame-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "frame-support", "frame-system", @@ -782,7 +788,7 @@ dependencies = [ [[package]] name = "frame-election-provider-solution-type" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -793,7 +799,7 @@ dependencies = [ [[package]] name = "frame-election-provider-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "frame-election-provider-solution-type", "frame-support", @@ -821,7 +827,7 @@ dependencies = [ [[package]] name = "frame-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "bitflags", "frame-metadata", @@ -853,7 +859,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "Inflector", "cfg-expr", @@ -867,7 +873,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate", @@ -879,7 +885,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "proc-macro2", "quote", @@ -889,7 +895,7 @@ dependencies = [ [[package]] name = "frame-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "frame-support", "log", @@ -1788,7 +1794,7 @@ checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" [[package]] name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -1812,7 +1818,7 @@ dependencies = [ [[package]] name = "pallet-election-provider-support-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -1825,7 +1831,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "frame-support", "frame-system", @@ -2720,7 +2726,7 @@ dependencies = [ [[package]] name = "sp-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "hash-db", "log", @@ -2738,7 +2744,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "blake2", "proc-macro-crate", @@ -2764,7 +2770,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "parity-scale-codec", "scale-info", @@ -2793,7 +2799,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "integer-sqrt", "num-traits", @@ -2855,8 +2861,9 @@ dependencies = [ [[package]] name = "sp-core" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ + "array-bytes", "base58", "bitflags", "blake2", @@ -2866,7 +2873,6 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "hex", "impl-serde", "lazy_static", "libsecp256k1", @@ -2915,7 +2921,7 @@ dependencies = [ [[package]] name = "sp-core-hashing" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "blake2", "byteorder", @@ -2929,7 +2935,7 @@ dependencies = [ [[package]] name = "sp-core-hashing-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "proc-macro2", "quote", @@ -2951,7 +2957,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "proc-macro2", "quote", @@ -2973,7 +2979,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "environmental", "parity-scale-codec", @@ -2984,7 +2990,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -3024,7 +3030,7 @@ dependencies = [ [[package]] name = "sp-io" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "bytes", "futures", @@ -3067,7 +3073,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "async-trait", "futures", @@ -3083,7 +3089,7 @@ dependencies = [ [[package]] name = "sp-npos-elections" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "parity-scale-codec", "scale-info", @@ -3108,7 +3114,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "backtrace", "lazy_static", @@ -3141,7 +3147,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "either", "hash256-std-hasher", @@ -3182,7 +3188,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -3213,7 +3219,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "Inflector", "proc-macro-crate", @@ -3225,7 +3231,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "parity-scale-codec", "scale-info", @@ -3260,7 +3266,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "hash-db", "log", @@ -3288,7 +3294,7 @@ checksum = "14804d6069ee7a388240b665f17908d98386ffb0b5d39f89a4099fc7a2a4c03f" [[package]] name = "sp-std" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" [[package]] name = "sp-storage" @@ -3307,7 +3313,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "impl-serde", "parity-scale-codec", @@ -3333,7 +3339,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "parity-scale-codec", "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", @@ -3361,7 +3367,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "ahash", "hash-db", @@ -3384,7 +3390,7 @@ dependencies = [ [[package]] name = "sp-version" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "impl-serde", "parity-scale-codec", @@ -3401,7 +3407,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -3425,7 +3431,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "impl-trait-for-tuples", "log", @@ -3437,7 +3443,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#7202ca616799a1d78e37ae8ec0093f16c49417c6" +source = "git+https://github.com/paritytech/substrate?branch=master#87224cf2cdafbacd0acac33c43a1063c02e02147" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec",